Learn About Lazy Loading In Angular

In this article, we are going to cover.

  • Lazy Loading of Feature Modules
  • Different Loading Strategies
  • Custom Preloading Strategy for Lazy-Loaded Modules

Lazy Loading of Feature Modules

Lazy loading is the process of loading some features of your Angular application only when you navigate to their routes for the first time! This can be useful for increasing your app performance and decreasing the initial size of the bundle that would be downloaded to a user’s browser.

Lazy-loaded modules can have one or more components inside. But remember the module which you are going to lazy load needs to be a different module than your Root Module.

So, for example, if you want to lazy load a product component then you have to create a product module with all the components it needs, along with its own routing file and module file.

We all know lazy loading is one of the most useful concepts of Angular Routing so let us see how to implement it by this simple example.

Firstly, I have created a new blank Angular Application with Angular-CLI and then I have added 3 components to it; namely Home, Product, and Employee. I have also created one component for Menu. As I have used the Menu component in this example I am using Router-Outlet to show the data of components that are going to be loaded outside that Menu area.

Okay, so below is the code for menu.component.html and app.component.html respectively.

menu.component.html

<ul class="nav nav-pills">
  <li class="nav-item">
    <a class="nav-link" routerLink="/home" routerLinkActive="active" [routerLinkActiveOptions]="{exact:true}">Home</a>
  </li>
  <li class="nav-item">
    <a class="nav-link" routerLink="/employee" routerLinkActive="active" [routerLinkActiveOptions]="{exact:true}">Employee List</a>
  </li>
  <li class="nav-item">
    <a class="nav-link" routerLink="/product" routerLinkActive="active" [routerLinkActiveOptions]="{exact:true}">Product List</a>
  </li>
  <li class="nav-item">
    <a class="nav-link" routerLink="/product/add" routerLinkActive="active" [routerLinkActiveOptions]="{exact:true}">Add Product</a>
  </li>
</ul>

app.component.html

<app-menu></app-menu>
<router-outlet></router-outlet>

So to start with lazy loading, we go to the route configuration (app.routing.ts) and use the property loadChildren.

Let us see the syntax of the loadChildren property.

const arr: Routes = [
  {
    path: 'employee',
    loadChildren: './employee/employee.module#EmployeeModule'
  }
];

The loadChildren property accepts a string value that contains the route to your lazy-loaded module followed by a Hash symbol and then the class name of that module.

Check the whole app.routing.ts file below.

import { Routes, RouterModule } from '@angular/router';
import { HomeComponent } from './home/home.component';

const arr: Routes = [
  { path: '', redirectTo: '/home', pathMatch: 'full' },
  { path: 'home', component: HomeComponent },
  { path: 'product', loadChildren: './product/product.module#ProductModule' },
  { path: 'employee', loadChildren: './employee/employee.module#EmployeeModule' },
  { path: '**', component: HomeComponent }
];
export const routingArr = RouterModule.forRoot(arr);

Here, notice that we are not going to import those modules that we want to lazy load. In our case we want to lazy load Employee and Product modules so we do not write import statements for them.

So when the route gets activated, this loadChildren property will get activated and it will load the requested module by its given path. Then it will load the requested component and display that component’s template (Html).

Now the next task is to configure our Routes for these feature modules.

So, as I said earlier we need to create separate Routing files and Module files for each module whichever we want to lazy load.

So below is the code for the employee.routing.ts file,

import { Routes, RouterModule } from '@angular/router';
import { EmployeeComponent } from './employee.component';

const arr: Routes = [
  { path: '', component: EmployeeComponent }
];

export const routingEmpArr = RouterModule.forChild(arr);

So here we are actually giving a path to all the components of the Employees Module created. This will actually find out the whole path of any component.

If you have more than one component in one Module, Like the Product Module in our example then it can be done as below,

product.routing.ts

import { Routes, RouterModule } from '@angular/router';
import { ProductComponent } from './product.component';
import { AddproductComponent } from './addproduct/addproduct.component';

const arr: Routes = [
  { path: '', component: ProductComponent },
  { path: 'add', component: AddproductComponent }
];

export const routingProductArr = RouterModule.forChild(arr);

And finally, create the Module files for each module. Here we need to create module files for both Employee and Product.

employee.module.ts

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { EmployeeComponent } from './employee.component';
import { routingEmpArr } from './employee.routing';

@NgModule({
  declarations: [
    EmployeeComponent
  ],
  imports: [
    CommonModule,
    routingEmpArr
  ]
})

export class EmployeeModule {}

product.module.ts

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { ProductComponent } from './product.component';
import { AddproductComponent } from './addproduct/addproduct.component';
import { routingProductArr } from './product.routing';

@NgModule({
  declarations: [
    ProductComponent,
    AddproductComponent
  ],
  imports: [
    CommonModule,
    routingProductArr
  ]
})

export class ProductModule {}

So, the last and most important thing is to make changes in the app.module.ts.

Here in app.module.ts, we will only load the Home Component. Importing any component to this file means that it will load all these components when we run this application in the browser. But here we want some of the modules to be loaded only on demand or we can say when we navigate to those modules.

app.module.ts

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { routingArr } from './app.routing';
import { AppComponent } from './app.component';
import { MenuComponent } from './menu.component';
import { HomeComponent } from './home/home.component';

@NgModule({
  declarations: [
    AppComponent,
    HomeComponent,
    MenuComponent
  ],
  imports: [
    BrowserModule,
    routingArr
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

So in this way, our lazy-loaded modules can be accessed.

Now, once we have lazy loaded, run this Application in the browser and go to the Network tab in the Console to see which files are generated.

Home works

Here you can see no files regarding Employee and Product Modules are there. This means that when the application gets loaded in the browser it will load only the necessary files it requires but now when we click on the Employee Menu then we can see the changes in the Network tab as below.

Employee list

As you can see a new bundle file (chunk) is generated for Employee. So this entire Employee module gets loaded in the browser when the user navigates to that particular route for the first time. This is how lazy loading gets implemented using the loadChildren property.

Similarly, you can also check the Product module.

Product list

Different Loading Strategies

Basically, we can load any Module in three different ways.

  • Eagerly
  • Lazily
  • Preloading

Eager loading

By default, every Angular Application uses this loading strategy. In Eager Loading all the feature modules are loaded before the application gets started along with the root (App) module. So if you have a small-sized application then you can use this strategy because it will require all the modules and their dependencies to be ready when you are running the application for the first time.

To implement the Eager Loading, you have to import the modules in the app.module.ts file just like what we do with any normal Angular Application.

Lazy Loading

As we have seen earlier if we use lazy loading then we can render the module on demand when needed. Also if the application size is becoming larger with many feature modules, loading everything eagerly will make the application slow. So instead we can use lazy loading.

Preloading

By preloading, the application will start loading chunks of modules before needed. This means modules do not wait to get loaded until users navigate to their routes. Modules can be loaded in the background asynchronously just after the application gets started.

For this strategy, Angular providesPreloadingStrategy abstract class. And it has two subclasses: PreloadAllModulesandNoPreloading.

  • NoPreloading— default behavior that doesn't preload any modules.
  • PreloadAllModules— all modules are preloaded as soon as possible.

To use any of the above strategies what you have to do is specify the strategy in your app-routing. module.as shown below.

  • import { RouterModule, Routes, PreloadAllModules } from '@angular/router';
  • Now inside @NgModule’s imports,
    imports: [
      ...
      RouterModule.forRoot(
        ROUTES,
        { preloadingStrategy: PreloadAllModules }
      )
    ],
    

Let's see a quick example of how we can use Preloading with Customization.

Custom Preloading Strategy for Lazy-Loaded Modules

If you do not want all lazy loadable modules to be preloaded then you can implement your own preloading strategy.

Ideally, we would like to preload the core features or most commonly used modules of our app. This will allow core features to load immediately as well and we will lazy load other features, which are less used, on demand when the user clicks the link.

So for this, we will define and export aCustomPreloadingclass that implementsPreloadingStrategy class.

Note. For Custom Preloading, I have created another fresh Angular Application which has Dashboard, Product, and Contact modules.

custom-preloading.ts

import { Observable, of } from 'rxjs';
import { PreloadingStrategy, Route } from '@angular/router';

export class CustomPreloading implements PreloadingStrategy {
  preload(route: Route, preload: () => Observable<any>): Observable<any> {
    if (route.data && route.data.preload) {
      return preload();
    } else {
      return of(null);
    }
  }
}

As you can see above we have implemented the PreloadingStrategy class so we need to override the Preload() method of that particular class. This method receives the active route as a parameter. With this route, it will look at whether the Data object was set or not. If set, then it will load that module otherwise it returns null which means it will not preload.

Now in our app.module.ts, we will import the PreloadingStrategy class, and define that the RouterModule should use the CustomPreLoading as our pre-loading strategy. After that, in the providers array, we will provide the path of this custom preloading file.

app.module.ts

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { AppComponent } from './app.component';
import { routes } from './app.routing';
import { RouterModule } from '@angular/router';
import { MenuComponent } from './menu.component';
import { CustomPreloading } from './custom-preloading';
import { DashboardComponent } from './dashboard/dashboard.component';

@NgModule({
  declarations: [
    AppComponent,
    MenuComponent,
    DashboardComponent
  ],
  imports: [
    BrowserModule,
    RouterModule.forRoot(routes, { preloadingStrategy: CustomPreloading }),
  ],
  providers: [ CustomPreloading ],
  bootstrap: [AppComponent]
})
export class AppModule { }

Now it’s time to set the Routes.

For customized pre-loading, we have to specify the Data object to the path that we define. This Data object has having property preload which should be set to true. So, the syntax is: data: {preload: true}

So, according to ourCustomPreloading strategy implementation, if the value of data.preload:truethen we are confirming that module is to be preloaded and if the preload value is not true then the module won’t be preloaded.

app.routing.ts

import { Routes } from '@angular/router';
import { DashboardComponent } from './dashboard/dashboard.component';

export const routes: Routes = [
  // Dashboard is Eager loaded
  { path: '', component: DashboardComponent, pathMatch: 'full' },
  { path: 'dashboard', component: DashboardComponent },

  // Contact will be Preloaded
  { path: 'contact', loadChildren: './contact/contact.module#ContactModule', data: { preload: true } },

  // Product will be Lazy loaded
  { path: 'product', loadChildren: './product/product.module#ProductModule' },

  { path: '**', component: DashboardComponent }
];

Okay, so finally run the application and check the Console’s Network tab.

Here notice that if you are loading the app for the first time, it will load both the Dashboard module and Contact Module. Because we have preloaded the Contact Module its chunk gets loaded initially.

Dashboard

And now click on the Product Menu. And you will notice that the chunk for that gets loaded at runtime when you actually visit that route.

Product

Thank you for reading!

You can find the full code for this here.