Lazy-loading NgModules in ASP.NET Core AngularSPA

0
18

Angular has this cool feature of loading a module lazily. Modules that are set up to load lazily can significantly save application startup time. Lazy-load module set up is done in application’s routing configuration section.

As the title suggests we will be using the AngularSPA template shipped with Visual Studio 2017 Preview (2) for demonstration.

Route that is configured to lazy-load a module sends an HTTP GET to the server which in turns return the module in a chunk of code block. This only happens when the router is activated for the first time in application lifecycle.

Here’s how the AppModuleShared (app.module.shared.ts) is setup in the AngularSPA starter project:

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { HttpModule } from '@angular/http';
import { RouterModule } from '@angular/router'; 
import { ReactiveFormsModule } from '@angular/forms';

import { AppComponent } from './components/app/app.component';
import { NavMenuComponent } from './components/navmenu/navmenu.component';
import { HomeComponent } from './components/home/home.component';
import { FetchDataComponent } from './components/fetchdata/fetchdata.component';
import { CounterComponent } from './components/counter/counter.component';

@NgModule({
 declarations: [
 AppComponent,
 NavMenuComponent,
 CounterComponent,
 FetchDataComponent,
 HomeComponent
 ],
 imports: [
 CommonModule,
 HttpModule,
 FormsModule,
 ReactiveFormsModule,
 RouterModule.forRoot([
 { path: '', redirectTo: 'home', pathMatch: 'full' },
 { path: 'home', component: HomeComponent },
 { path: 'counter', component: CounterComponent },
 { path: 'fetch-data', component: FetchDataComponent },
 { path: '**', redirectTo: 'home' }
 ])
 ]
})
export class AppModuleShared {
}

Let’s assume that the CounterComponent is one of the less accessed component. It would be performance worthy to put it under a separate module and load it on demand.

Add a counter.module.ts file under the counter folder and add the following module code:

import { NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';
import { CounterComponent } from './counter.component';

@NgModule({
 imports: [
 RouterModule.forChild([{ path: '', component: CounterComponent }])
 ],
 exports: [RouterModule],
 declarations: [CounterComponent]
})

export class CounterModule { }

Notice that the module has its own route configuration. Since CounterModule is a feature module, the forChild method is used instead of forRoot for routes configuration.

Modify the app.module.shared.ts and remove the CounterComponet references from the file:

  • Remove the import statement
  • Remove component declaration from the declarations array.

Following is the modified app.module.shared.ts code:

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { HttpModule } from '@angular/http';
import { RouterModule } from '@angular/router'; 
import { ReactiveFormsModule } from '@angular/forms';

import { AppComponent } from './components/app/app.component';
import { NavMenuComponent } from './components/navmenu/navmenu.component';
import { HomeComponent } from './components/home/home.component';
import { FetchDataComponent } from './components/fetchdata/fetchdata.component';

@NgModule({
 declarations: [
 AppComponent,
 NavMenuComponent,
 FetchDataComponent,
 HomeComponent
 ],
 imports: [
 CommonModule,
 HttpModule,
 FormsModule,
 ReactiveFormsModule,
 RouterModule.forRoot([
 { path: '', redirectTo: 'home', pathMatch: 'full' },
 { path: 'home', component: HomeComponent },
 { path: 'counter', loadChildren: './components/counter/counter.module#CounterModule' },
 { path: 'fetch-data', component: FetchDataComponent },
 { path: '**', redirectTo: 'home' }
 ])
 ]
})
export class AppModuleShared {
}

Now, we no longer have the component property; instead we replaced it with loadChilden. Property loadChildren takes a relative path to a module that would be lazy-loaded. Notice the module name itself is added at the end of the path string (#CounterModule). That is because CounterModule class is not the default export of the file.

The last piece of the configuration required is in the webpack.configure.js file. But first we need to install the angular2-router-loader package.

angular2-router-loader is a Webpack loader for Angular that enables string-based module loading with the Angular Router. Use the following npm install command to install the package:

npm install --save angular2-router-loader

Of course, change your command prompt’s directory to your application root before running the command

Moving to configuring the angular2-router-loader package in the webpack.configure.js. In the use array, add another entry for angular-router-loader along with awesome-typescript-loader?silent=true, angular2-template-loader. The module section should now look like the following:

module: {
 rules: [
 { test: /\.ts$/, include: /ClientApp/, use: ['awesome-typescript-loader?silent=true', 'angular2-template-loader', 'angular2-router-loader'] },
 { test: /\.html$/, use: 'html-loader?minimize=false' },
 { test: /\.css$/, use: ['to-string-loader', 'css-loader'] },
 { test: /\.(png|jpg|jpeg|gif|svg)$/, use: 'url-loader?limit=25000' }
 ]
}

When finished, build and run the application. To make sure the CounterComponent is coming from a lazy-loaded module, open the developer console of your browser and go to the network tab. Navigating to the counter route (using the side-menu) will now load a chunk of new code via an HTTP GET request.

Git Repository of the demo – https://github.com/fiyazbinhasan/AngularSPA

LEAVE A REPLY