Lazy loading of modules in Angular 2

In the last post I shortly mentioned the ability of Angular 2 router to load lazily the application modules.

This topic deserves more explanations, so let’s go into details about this feature.

The start point is an Angular 2 applications with a couple of indipendent modules; in orther to have better performances we want to load these module lazily.

Let’s go to the implementation details.

Modules

In the application we have the usual modules Customer and Invoice; the first one:


import { NgModule } from "@angular/core";
import { Routes, RouterModule } from "@angular/router";
import { HttpModule } from "@angular/http";
import { CustomerComponent } from "./customer.component";
......

@NgModule ({
imports: [
HttpModule,
RouterModule.forChild([
{
path: "customers",
component: CustomerComponent
}])
],
exports: [
RouterModule
],
declarations: [
CustomerComponent
]
})

export class CustomerModule {}

And the second one:

import { NgModule } from "@angular/core";
import { Routes, RouterModule } from "@angular/router";
import { HttpModule } from "@angular/http";
import { InvoiceComponent } from "./invoice.component";
......

@NgModule ({
imports: [
HttpModule,
RouterModule.forChild([
{
path: "invoices",
component: InvoiceComponent
}])
],
exports: [
RouterModule
],
declarations: [
InvoiceComponent
]
})

export class InvoiceModule {}

We have defined the roots for the features modules, that must be configured forChild, according with the Angular 2 specifications.

To enable them in the application, we have to import them in the main module:

import { NgModule } from "@angular/core";
import { HttpModule } from "@angular/http";
import { RouterModule } from "@angular/router";
import { CustomerModule } from "./customer/customer.module";
import { InvoiceModule } from "./invoice/invoice.module";
......

@NgModule ({
imports: [
HttpModule,
CustomerModule,
InvoiceModule,
RouterModule.forRoot([
{
path: "",
redirectTo: "customers",
pathMatch: "full"
}
])],
exports: [
RouterModule
],
......
})

export class AppModule {}

We have finished the configuration and the if we start the application, it’ll work as expected; but we can do more.

At the moment, all the modules of the application will be loaded at the startup of the application; this is not a news, we are not surprized about that, the old angular applications worked in this way.

But with angular 2 we can do more, we are able to load the modules lazily, when required by the application.

The only thing to do is to change the configuration of the router.

Router configuration

The main change concerns the AppModule, where we have to change the configuration of the router:

import { NgModule } from "@angular/core";
import { HttpModule } from "@angular/http";
import { RouterModule } from "@angular/router";
......

@NgModule ({
imports: [
HttpModule,
RouterModule.forRoot([
{
path: "",
redirectTo: "customers",
pathMatch: "full"
},
{
path: "customers",
loadChildren: "app/customer/customer.module#CustomerModule"
},
{
path: "invoices",
loadChildren: "app/invoice/invoice.module#InvoiceModule"
},
])],
exports: [
RouterModule
]
......
})

export class AppModule {}

Instead of import the feature modules, we have defined the path of them as “Childrens”.

What we need to do now is update the paths in the customer and invoice modules:

@NgModule ({
imports: [
......
RouterModule.forChild([
{
path: "",
component: CustomerComponent
}])
],
......
})
@NgModule ({
 imports: [ 
 ......
 RouterModule.forChild([
 {
 path: "",
 component: InvoiceComponent
 }])
 ],
 ......
})

In short, we have changed the default path of the modules, because we declared these in the AppModule above.

The configuration is pretty simple but we can obtain a huge performance improvement.

Consider that in big applications, the modules wont be loaded eagerly and as a result the startup of the application will be very fast.

Here the full Angular2 project.

 

Advertisements
Lazy loading of modules in Angular 2

Feature modules in Angular 2

Angular 2 give us the chance to organize our application in modules.

Briefly, a module is a library where there may be components, directives and pipes and define a block of functionalities; Angular 2 libraries itself are modules, like FormsModule, HttpModule.

One cool behaviour of the modules is the ability to be loaded eagerly or lazyly through the router; this is a huge improvement, because we are able to load a module only when required and not at the startup of the application; we’ll see that the router help us in this phase.

What we’ll do in this example is define two commons modules, shared in the application; then we’ll define the feature modules, that are modules with a specific purphose; finally we’ll import them in the root module of the application.

Shared modules

In every application we have some stuff that we want to share, libraries such as translations, bootstrap, rxjs, and angular modules like FormsModule and even CommonsModule.

For these, the better choice is define a shared module that import these libraries:


import { NgModule, ModuleWithProviders, Optional, SkipSelf } from "@angular/core";
import { FormsModule } from "@angular/forms";
import { CommonModule } from "@angular/common";
import { TranslateModule } from "ng2-translate";
import { ModalModule, DatepickerModule } from 'ng2-bootstrap/ng2-bootstrap';

import "rxjs/add/observable/throw";
import "rxjs/add/observable/forkJoin";
import "rxjs/add/operator/catch";
import "rxjs/add/operator/debounceTime";
import "rxjs/add/operator/distinctUntilChanged";
import "rxjs/add/operator/map";
import "rxjs/add/operator/switchMap";
import "rxjs/add/operator/toPromise";

@NgModule ({
imports: [
FormsModule,
CommonModule,
ModalModule.forRoot(),
DatepickerModule.forRoot(),

],
exports: [
FormsModule,
CommonModule,
TranslateModule,
ModalModule,
DatepickerModule
]
})

export class SharedModule {}

What we done is import the bunch of modules that we want to include, then we defined the list of imported/exported modules.

If importing the modules is obvious, we can’t says the same thing about the export; we need to export the modules in order to make them shared; otherwise the others modules couldn’t access to these libraries.

Another behaviour that deserves a mention is the forRoot static method of the ModalModule and the DatepickerModule.

Angular 2 has a convention, so when a module has singleton services needs to implement a static method forRoot; otherwise we could provide these services, but thus the other modules could import these shared module and instantiate them.

The second module that we implement is the CoreModule, that contains the singleton services of the application:


import { NgModule, ModuleWithProviders, Optional, SkipSelf } from "@angular/core";
import { ToastModule } from "ng2-toastr/ng2-toastr";
import { SearchService } from "./search.service";
import { AlertService } from "./alert.service";

let options: any = {
autoDismiss: true,
positionClass: 'toast-bottom-right',
};

@NgModule ({
imports: [
ToastModule.forRoot(options)
]
})

export class CoreModule {
constructor (@Optional() @SkipSelf() parentModule: CoreModule) {
if (parentModule) {
throw new Error('CoreModule is already loaded. Import it in the AppModule only');
}
}

static forRoot(): ModuleWithProviders {
return {
ngModule: CoreModule,
providers: [
SearchService,
ToastModule,
AlertService
]
};
}
}

First of all, we import the ToastModule as singleton, by calling the forRoot method and passing the configuration as a parameter.

As explained above, because this module provide singleton services, have to implement a static forRoot method.

The constructor of the module class deserves an additional comment; this module must be loaded only from the root module, so to disallow the import from a feature module, we check this and eventually we throw an exception.

Feature modules

We can now implement our feature modules, that have everyone a specific purphose in the application.

For example, a CustomerModule will look like this:


import { NgModule } from "@angular/core";
import { Routes, RouterModule } from "@angular/router";
import { HttpModule } from "@angular/http";

import { SharedModule } from "../shared/shared.module";
import { CityModule } from "../city/city.module";
import { CustomerComponent } from "./customer.component";
import { CustomerDetailComponent } from "./customer.detail.component";
import { SearchCustomersPipe } from "./searchCustomers.pipe";
import { CustomerService } from "./customer.service";

@NgModule ({
imports: [
HttpModule,
SharedModule,
CityModule,
RouterModule.forChild([
{
path: "",
component: CustomerComponent
}])
],
exports: [
RouterModule
],
declarations: [
CustomerComponent,
CustomerDetailComponent,
SearchCustomersPipe
],
providers: [
CustomerService
]
})

export class CustomerModule {}

You can notice that the module import the shared module defined above.

The module deals to import the modules that needs and define with the static method forChild the root paths for the module.

Then declare the components and provide the services that belongs to the module.

App module

The last module is the root module, that is the startup module.

This module imports the shared modules of the application and define the feature modules:


import { NgModule } from "@angular/core";
import { HttpModule } from "@angular/http";
import { RouterModule } from "@angular/router";
import { BrowserModule } from "@angular/platform-browser";
import { TranslateModule } from "ng2-translate";

import { SharedModule } from "./shared/shared.module";
import { CoreModule } from "./core/core.module";
import { AppComponent } from "../app/app.component";
import { HeaderComponent } from "./header.component";

@NgModule ({
imports: [
BrowserModule,
HttpModule,
TranslateModule.forRoot(),
CoreModule.forRoot(),
SharedModule,
RouterModule.forRoot([
{
path: "",
redirectTo: "customers",
pathMatch: "full"
},
{
path: "customers",
loadChildren: "app/customer/customer.module#CustomerModule"
},
{
path: "invoices",
loadChildren: "app/invoice/invoice.module#InvoiceModule"
},
])],
exports: [
RouterModule
],
declarations: [
AppComponent,
HeaderComponent
],
bootstrap: [
AppComponent
]
})

export class AppModule {}

As you can see, the CoreModule is imported with the forRoot method, which means that is singleton as well as the RouterModule.

We defined the root paths of the router by loading the concerning modules as childrens; thus they will be loaded lazy.

So, these modules and the dependent modules will be loaded the first time only once the routing path will be fired; this is a awesome feature of Angular 2.

Here the full Angular2 project.

 

Feature modules in Angular 2