Angular modularity and lifecycle hooks
Angular modularity with NgModules provides a great way to organize the code in a web application. Many third-party libraries, such as PrimeNG, Angular Material, Ionic, are distributed as NgModules. Lifecycle hooks allow us to perform custom logic at component level at a well-defined time. This section covers these major concepts in detail.
Modules and bootstrapping
Angular modules make it possible to consolidate components, directives, services, pipes, and many more into cohesive blocks of functionality. Angular's code is modularized. Every module has its own functionality. There are FormsModule
, HttpModule
, RouterModule
, and many other modules as well. What does a module look like? A module is a class annotated with the @NgModule
decorator (imported from @angular/core
). @NgModule
takes a configuration object that tells Angular how to compile and run the module code. The most significant properties of the the configuration object are:
declarations
: The array with components, directives, and pipes, which are implemented in that module and belong to that module.imports
: The array with dependencies in form of other modules which need to be made available to that module.exports
: The array of components, directives, and pipes to be exported and permitted to be imported by another modules. The rest is private. This is the module's public API and similar to how theexport
keyword works in ECMAScript modules.providers
: This is the array of services (service classes, factories, or values), which are available in that module. Providers are parts of the module and can be injected into components (inclusive sub-components), directives, and pipes defined within the module.bootstrap
: Every Angular application has at least one module--the root module. Thebootstrap
property is only used in the root module and contains the component which should be instantiated first when bootstrapping the application.entryComponents
: This is the array of components that Angular generates component factories for. Normally, you need to register a component as an entry component when it is intended to be created dynamically at runtime. Such components can not be figured out automatically by Angular at template compilation time.
A typical module configuration for any separate example in this book looks something like this:
import {NgModule} from '@angular/core'; import {BrowserModule} from '@angular/platform-browser'; import {BrowserAnimationsModule} from '@angular/platform-browser/animations'; import {FormsModule} from '@angular/forms'; import {APP_BASE_HREF} from '@angular/common'; // PrimeNG modules needed in this example import {ButtonModule} from 'primeng/components/button/button'; import {InputTextModule} from 'primeng/components/inputtext/inputtext'; import {AppComponent} from './app.component'; import {SectionComponent} from './section/section.component'; import {routes} from './app-routing.module'; @NgModule({ imports: [BrowserModule, BrowserAnimationsModule, FormsModule, routes, ButtonModule, InputTextModule], declarations: [AppComponent, SectionComponent], providers: [{provide: APP_BASE_HREF, useValue: '/'}], bootstrap: [AppComponent] }) export class AppModule { }
Note
BrowserModule
is needed to get access to the browser-specific renderers and Angular standard directives such as ngIf
and ngFor
. Don't import BrowserModule
in any other modules except the root module. Feature modules and lazy-loaded modules should import CommonModule
instead.
The following is an example of how to bootstrap an Angular application in the JIT mode (just in time compilation):
import {platformBrowserDynamic} from '@angular/platform-browser-dynamic'; import {AppModule} from './app'; platformBrowserDynamic().bootstrapModule(AppModule);
In the ahead-of-time mode (AOT compilation), you need to provide a factory class. To generate the factory class, you must run the ngc
compiler instead of the TypeScript tsc
compiler. In the last two sections of this chapter, you will see how to use AOT with Webpack and Angular CLI. The bootstrapping code in the AOT mode looks like the following:
import {platformBrowser} from '@angular/platform-browser'; import {AppModuleNgFactory} from './app.ngfactory'; platformBrowser().bootstrapModuleFactory(AppModuleNgFactory);
Note
Templates with bindings written in Angular need to be compiled. With AOT, the compiler runs once at build time. With JIT, it runs every time at runtime. Browsers load a pre-compiled version of the application much faster and there is no need to download the Angular compiler if the app is already compiled.
Modules can also be lazy loaded when they get requested (on demand). This approach reduces the size of web resources loaded on initial page display. The page appears faster. If you want to enable lazy loading, you have to configure the router to load the module lazy. All you need is a path
object with a loadChildren
property, which points to the path and name of the lazy loaded module:
{path: "section", loadChildren: "app/section/section.module#SectionModule"}
Note that the value of loadChildren
property is a string. Furthermore, the module importing this router configuration should not declare the lazy loaded module as dependency in the imports
property of the configuration object.
Lifecycle hooks
Angular components come with lifecycle hooks, which get executed at specific times in the component's life. For this purpose, Angular offers different interfaces. Each interface has a method of the same name as the interface name with the prefix ng
. Each method is executed when the corresponding lifecycle event occurs. They are also called lifecycle hook methods. Angular calls the lifecycle hook methods in the following sequence after the constructor has been called:
The lifecycle hook method | Purpose and timing |
| This is called whenever one or more data-bound input properties change. This method is called on initial changes (before |
| This is called once, after the first |
| This is called during every change detection run. It is a good place for custom logic, which allows us to do a fine-grained check of which property on our object changed. |
| This is called once, after Angular puts external content into the component's view. A placeholder for any external content is marked with the |
| This is called after Angular checks the content put into the component's view. |
| This is called once, after Angular initializes the component's and child's views. |
| This is called after Angular checks the component's views and child views. |
| This is called just before Angular destroys the component's instance. This happens when you remove the component with built-in structural directives such as |
Let's see an example of how to use ngOnInit
and ngOnChanges
:
import {Component, OnInit, OnChanges, SimpleChange} from '@angular/core'; @Component({ selector: 'greeting-component', template: `<h1>Hello {{text}}</h1>` }) export class GreetingComponent implements OnInit, OnChanges { @Input text: string; constructor() { } ngOnInit() { text = "Angular"; } ngOnChanges(changes: {[propertyName: string]: SimpleChange}) { console.log(changes.text); // changes = {'text': {currentValue: 'World', previousValue: {}}} // changes = {'text': {currentValue: 'Angular', previousValue: 'World'}} } }
Usage in HTML:
<greeting-component [text]="World"></greeting-component>
Let's now see how to use the ngContent
directive:
export @Component({ selector: 'greeting-component', template: `<div><ng-content></ng-content> {{text}}</div>` }) class GreetingComponent { @Input text: string; }
Usage in HTML:
<greeting-component [text]="World"><b>Hello</b></greeting-component>
After the component's initialization, the following hook methods get always executed on every change detection run: ngDoCheck
-> ngAfterContentChecked
-> ngAfterViewChecked
-> ngOnChanges
.