Lazy-Loading NgModules In ASP.NET Core Angular SPA

This article shows you how configure ASP.NET Core Angular SPA starter project to serve a lazy-loaded Angular module.

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 setup is done in the application’s routing configuration section.

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

ASP.NET Core

Route that is configured to lazy-load a module sends an HTTP GET to the server which in turn returns 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 Angular SPA starter project.

  1. import { NgModule } from '@angular/core';  
  2. import { CommonModule } from '@angular/common';  
  3. import { FormsModule } from '@angular/forms';  
  4. import { HttpModule } from '@angular/http';  
  5. import { RouterModule } from '@angular/router';   
  6. import { ReactiveFormsModule } from '@angular/forms';  
  7.   
  8. import { AppComponent } from './components/app/app.component';  
  9. import { NavMenuComponent } from './components/navmenu/navmenu.component';  
  10. import { HomeComponent } from './components/home/home.component';  
  11. import { FetchDataComponent } from './components/fetchdata/fetchdata.component';  
  12. import { CounterComponent } from './components/counter/counter.component';  
  13.   
  14. @NgModule({  
  15.     declarations: [  
  16.         AppComponent,  
  17.         NavMenuComponent,  
  18.         CounterComponent,  
  19.         FetchDataComponent,  
  20.         HomeComponent  
  21.     ],  
  22.     imports: [  
  23.         CommonModule,  
  24.         HttpModule,  
  25.         FormsModule,  
  26.         ReactiveFormsModule,  
  27.         RouterModule.forRoot([  
  28.             { path: '', redirectTo: 'home', pathMatch: 'full' },  
  29.             { path: 'home', component: HomeComponent },  
  30.             { path: 'counter', component: CounterComponent },  
  31.             { path: 'fetch-data', component: FetchDataComponent },  
  32.             { path: '**', redirectTo: 'home' }  
  33.         ])  
  34.     ]  
  35. })  
  36. export class AppModuleShared {  
  37. }   

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 

  1. import { NgModule } from '@angular/core';  
  2. import { RouterModule } from '@angular/router';  
  3. import { CounterComponent } from './counter.component';  
  4.   
  5. @NgModule({  
  6.     imports: [  
  7.         RouterModule.forChild([{ path: '', component: CounterComponent }])  
  8.     ],  
  9.     exports: [RouterModule],  
  10.     declarations: [CounterComponent]  
  11. })  
  12.   
  13. 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 importstatement
  • Remove component declaration from the declarations

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

  1. import { NgModule } from '@angular/core';  
  2. import { CommonModule } from '@angular/common';  
  3. import { FormsModule } from '@angular/forms';  
  4. import { HttpModule } from '@angular/http';  
  5. import { RouterModule } from '@angular/router';   
  6. import { ReactiveFormsModule } from '@angular/forms';  
  7.   
  8. import { AppComponent } from './components/app/app.component';  
  9. import { NavMenuComponent } from './components/navmenu/navmenu.component';  
  10. import { HomeComponent } from './components/home/home.component';  
  11. import { FetchDataComponent } from './components/fetchdata/fetchdata.component';  
  12.   
  13. @NgModule({  
  14.     declarations: [  
  15.         AppComponent,  
  16.         NavMenuComponent,  
  17.         FetchDataComponent,  
  18.         HomeComponent  
  19.     ],  
  20.     imports: [  
  21.         CommonModule,  
  22.         HttpModule,  
  23.         FormsModule,  
  24.         ReactiveFormsModule,  
  25.         RouterModule.forRoot([  
  26.             { path: '', redirectTo: 'home', pathMatch: 'full' },  
  27.             { path: 'home', component: HomeComponent },  
  28.             { path: 'counter', loadChildren:   
  29.             './components/counter/counter.module#CounterModule' },  
  30.             { path: 'fetch-data', component: FetchDataComponent },  
  31.             { path: '**', redirectTo: 'home' }  
  32.         ])  
  33.     ]  
  34. })  
  35. export class AppModuleShared {  
  36. }   

Now, we no longer have the component property; instead, we replaced it with loadChildren. 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:

  1. 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.

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

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.

ASP.NET Core

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