Lazy Loading using Dynamic Routes - Lab 11

Hey friends welcome to Learn Angular Step by Step site, This is lab 11 article of an angular lab series, here we will understand Lazy Loading using Dynamic Routes in Angular.

Download Source Code For This Lab

Source Code

Previous Labs

In lab 6 we have covered basic fundamentals of Angular like module, components, directives, expression and so on. To see this lab read from here.

In lab 7 we will look in to how to create Single page applications using Angular routing. To see Lab 7 you need to read from here.

In lab 8 we will understand how to implement validations using form, formgroup and formcontrol. You can see lab 8 by clicking on this link.

In lab 9 we will see how to make HTTP post and get calls to AP services using Angular. You can read the lab from here.

In lab 10 we will understand how to make reusable angular UI components using Input , output and emitters. You can read this lab from here.

Theory

Big projects will have lot of components and modules, in other words we will end up with lot of JS files on the browser client side. Loading these JS files in ONE GO at the client browser side would really hit performance.

If you load the current application at this stage and see the developer tools you will see on the network tab all JS files are getting loaded at the start.

When the first time the user comes to the site we would like to just load the welcome component and master component JS only.

When the user clicks on supplier and customer respective JS files should be loaded at that moment.

 

Let’s investigate who is the culprit ?.

If you see the current architecture of our project we have one module(MainModule.ts) and all components current belong to this only ONE Module. So when this module loads it loads all components inside it.

In simple words we need to BREAK MODULES in to separate physical module files.

Step 1 :- Creating three different physical modules

So as discussed in the previous part of the theory we need to first divide our project in to three different physical module files: - MainModule , SupplierModule and CustomerModule.

So in the module folder lets create three different physical module files. We already have MainModule.ts we need to create two more.

MainModule.ts :- This module will load "MasterPageComponent.ts" and "WelcomeComponent.ts".

SupplierModule.ts :- This module will load "SupplierComponent.ts".

CustomerModule.ts :- This will load CustomerComponent and GridComponent. Remember grid is used only in Customer UI so this should load only when Customer functionality is loaded.

Step 2 :- Removing Supplier and Customercomponent from MainModule

The first thing is we need to remove all references of CustomerComponent,SupplierComponent and GridComponent from the MainModule. Below is the striked out source code which needs to be removed from the MainModule. In the main module, we only have reference to WelcomeComponent and MastePageComponent.

Two modules are decoupled from each other when import does not exist between those modules. Even if you do not use the component and there is an import decoupling is not complete and the JS will be loaded.

Lot of Code has been removed for clarity. Please download source code
for full code.
<strike>import { CustomerComponent }   from '../Component/CustomerComponent';</strike>
<strike>import { SupplierComponent }   from '../Component/SupplierComponent';</strike>
import { WelcomeComponent }   from '../Component/WelcomeComponent';
<strike>import { GridComponent }   from '../Component/GridComponent';</strike>
import { MasterPageComponent }   from '../Component/MasterPageComponent';

@NgModule({
    imports: [RouterModule.forRoot(ApplicationRoutes), 
        InMemoryWebApiModule.forRoot(CustomerApiService),
             BrowserModule,ReactiveFormsModule,
             FormsModule,HttpModule],
    declarations: [<strike>CustomerComponent</strike>,
                MasterPageComponent,
                <strike>SupplierComponent</strike>,
                WelcomeComponent, 
                <strike>GridComponent</strike>],
    bootstrap: [MasterPageComponent]
})
export class MainModuleLibrary { }

Step 3 :- Creating different Route files

As said previously "A SIMPLE IMPORT REFERENCE" will make two modules coupled. If the modules are coupled those JS files will be loaded.

If you remember "MainModule.ts" loads the Routes from "Routing.ts" and Routing.ts has import references to SupplierComponent and CustomerComponent.

So loading routing will load the other components as well and we will not be able to achieve lazy loading.

So let us remove all references of Customer and Supplier component from MainModule.ts , see the striked out code down below.

import {Component} from '@angular/core';
<strike>import {CustomerComponent} from '../Component/CustomerComponent';</strike>
<strike>import {SupplierComponent} from "../Component/SupplierComponent";</strike>
import {WelcomeComponent} from "../Component/WelcomeComponent";
export const ApplicationRoutes = [
<strike>    { path: 'Customer', component: CustomerComponent },</strike>
<strike>    { path: 'Supplier', component: SupplierComponent },</strike>
     { path: '', component:WelcomeComponent  },
    { path: 'UI/Index.html', component:WelcomeComponent  }
];

But still we need to still define routes for "Customer" and "Supplier" and the same time not use "import" statement as that makes the module coupled. If you look at the current syntax of defining route we need to have that component in the import or else we cannot define the route.

{ path: 'CustomerComponent', component:CustomerComponent  },

For that Angular has given a nice property called as "loadChildren". In "loadChildren" we need to give the module in a single quote like a string. Which means that this thing will be evaluated during run time and now compile time.

{ 
path: 'Customer',
loadChildren:'../Module/CustomerModuleLibrary#CustomerModuleLibrary' 
}

The structure of "loadChildren" should follow this pattern: -

  • The first element in the 'loadChildren' is the folder name, in case module file is in a folder.
  • The second element is the physical filename of the module. In our case the we have "CustomerModuleLibrary.ts" , "SupplierModuleLibrary.ts" and so on.
  • The third element after the "#" is the class name which should be loaded from the physical module file. It’s very much possible you can have many classes in one physical module file , but after the "#" we define which of those classes should be loaded.

The full code of the route will look something as shown below.

import {Component} from '@angular/core';
import {WelcomeComponent} from "../Component/WelcomeComponent";
export const ApplicationRoutes = [
    { path: 'Customer', 
loadChildren:'../Module/CustomerModuleLibrary#CustomerModuleLibrary' },
    { path: 'Supplier', 
loadChildren: '../Module/SupplierModuleLibrary#SupplierModuleLibrary' },
     { path: '', component:WelcomeComponent  },
    { path: 'UI/Index.html', component:WelcomeComponent  },
    { path: 'UI', component:WelcomeComponent  }
];

We also need to create two more route files one for "Customer" and one for "Supplier" as shown below.

import {Component} from '@angular/core';
import {CustomerComponent} from "../Component/CustomerComponent";
export const CustomerRoutes = [
    { path: 'Add', component:CustomerComponent  }
];
import {Component} from '@angular/core';
import {SupplierComponent} from "../Component/SupplierComponent";
export const SupplierRoutes = [
    { path: 'Add', component:SupplierComponent  }
];

"SupplierRoutes" and "CustomerRoutes" are child routes while the "ApplicationRoutes" is the parent route.

Step 4 :- Calling Child routes in Supplier and Customer modules

In supplier module and customer modules we need to load their respective routes defined in "Step 3". To load child routes we need to use "RouterModule.forChild".

import { NgModule }      from '@angular/core';
import { CommonModule } from '@angular/common';
import {FormsModule , ReactiveFormsModule} from "@angular/forms"
import { SupplierComponent }   from '../Component/SupplierComponent';
import { RouterModule }   from '@angular/router';
import { SupplierRoutes }   from '../Routing/SupplierRouting';   
import {CustomerApiService} from "../Api/CustomerApi"
@NgModule({
    imports: [RouterModule.forChild(SupplierRoutes),
             CommonModule,ReactiveFormsModule,
             FormsModule],
    declarations: [SupplierComponent],
    bootstrap: [SupplierComponent]
})
export class SupplierModuleLibrary { }

Same way we need to for Customer Module.

import { NgModule }      from '@angular/core';
import { CommonModule } from '@angular/common';
import {FormsModule , ReactiveFormsModule} from "@angular/forms"
import { CustomerComponent }   from '../Component/CustomerComponent';
import { GridComponent }   from '../Component/GridComponent';
import { RouterModule }   from '@angular/router';
import { CustomerRoutes }   from '../Routing/CustomerRouting';  
import { InMemoryWebApiModule } from 'angular2-in-memory-web-api';
import {CustomerApiService} from "../Api/CustomerApi"
import { HttpModule } from '@angular/http';

@NgModule({
    imports: [RouterModule.forChild(CustomerRoutes), 
        InMemoryWebApiModule.forRoot(CustomerApiService),
             CommonModule,ReactiveFormsModule,
             FormsModule,HttpModule],
    declarations: [CustomerComponent, 
                GridComponent],
    bootstrap: [CustomerComponent]
})
export class CustomerModuleLibrary { }

Step 5 :- Configuring routerlinks

In step 3 and 4 we have defined parent routes and child routes. Parent routes are defined in "Routing.ts" while child routes are defined in "CustomerRouting.ts" and "SupplierRouting.ts". So now the router link has to be changed to "Supplier/Add" and "Customer/Add" as shown in the below code.

<a [routerLink]="['Supplier/Add']">Supplier</a> <br />
<a [routerLink]="['Customer/Add']">Customer</a><br>

So now the full code look of master page looks as shown below.

<table border="0" width="100%">
<tr>
<td width="20%"><img src="http://www.questpond.com/img/logo.jpg" alt="Alternate Text" />
</td>
<td width="80%">Questpond.com Private limited</td>
</tr>
<tr>
<td valign=top><u>Left Menu</u><br />
<a [routerLink]="['Supplier/Add']">Supplier</a> <br />
<a [routerLink]="['Customer/Add']">Customer</a><br>
<a [routerLink]="['']">Home</a>
</td>
<td>
<div id="dynamicscreen">
<router-outlet></router-outlet>
</div>
</td>
</tr>
<tr>
<td></td>
<td>Copy right @Questpond</td>
</tr>
</table>

Step 6 :- Replacing browser module with common module

"BrowserModule" and "CommonModule" are modules of angular. "BrowserModule" has code which starts up services and launches the application while "CommonModule" has directives like "NgIf" and "NgFor".
"BrowserModule" re-exports "CommonModule". Or if I put in simple words "BrowserModule" uses "CommonModule". So if you are loading "BrowserModule" you are loading "CommonModule" also.

So now if you are loading "BrowserModule" in all three modules then you will end uploading "CommonModule" 3 times. When you are doing Lazy Loading you really do not want to load things 3 times , it should be loaded only once.

So if you have "BrowserModule" in all three modules then you would end up getting such kind of error as shown in below figure.

This error says "BrowserModule" has already been loaded in the "MainModule" please use "CommonModule" in "CustomerModule" and "SupplierModule".

 

So in main module load the browser module and in rest of modules load "CommonModule".

import { BrowserModule } from '@angular/platform-browser';
// Other imports have been removed for clarity
@NgModule({
    imports: [RouterModule.forRoot(ApplicationRoutes), 
        InMemoryWebApiModule.forRoot(CustomerApiService),
             BrowserModule,ReactiveFormsModule,
             FormsModule,HttpModule],
    declarations: [
                MasterPageComponent,
                WelcomeComponent],
    bootstrap: [MasterPageComponent]
})
export class MainModuleLibrary { }

But in customer and supplier module just load common module.

import { CommonModule } from '@angular/common';
// other imports has been removed for clarity

@NgModule({
    imports: [RouterModule.forChild(SupplierRoutes),
             CommonModule,ReactiveFormsModule,
             FormsModule],
    declarations: [SupplierComponent],
    bootstrap: [SupplierComponent]
})
export class SupplierModuleLibrary { }
import { CommonModule } from '@angular/common';
// Other import has  been removed for claroty
@NgModule({
    imports: [RouterModule.forChild(CustomerRoutes), 
        InMemoryWebApiModule.forRoot(CustomerApiService),
             CommonModule,ReactiveFormsModule,
             FormsModule,HttpModule],
    declarations: [CustomerComponent, 
                GridComponent],
    bootstrap: [CustomerComponent]
})
export class CustomerModuleLibrary { }

Step 7 :- Check if Lazy loading is working

Now run your application, go to network tab and check if lazy loading is working. You can see when the application run at the start only "WelcomeComponent" and "MasterPageComponent" is loaded. Once you click on supplier and customer the respective components will loaded at that time.

Please put a proper filter so that you do not see all JS files in your network.

Download Source Code For This Lab

Source Code

+91-022-49786776
+91 9967590707
questpond@questpond.com / questpond@gmail.com