Page Banner

Angular2 Lazy Loading

Lazy loading speeds up angular2 application load time by splitting it into multiple bundles, and loading them on demand.

Consider an example:

import {Component, NgModule} from ‘@angular/core’;
import {RouterModule} from ‘@angular/router’;
import {platformBrowserDynamic} from ‘@angular/platform-browser-dynamic’;
@Component({…}) class MailAppCmp {}
@Component({…}) class ConversationsCmp {}
@Component({…}) class ConversationCmp {}
@Component({…}) class ContactsCmp {}
@Component({…}) class ContactCmp {}
const ROUTES = [
  {
    path: 'conversations/:folder',
    children: [
      { path: '', component: ConversationsCmp },
      { path: ':id', component: ConversationCmp, children: […]}
    ]
  },
 {
    path: 'contacts',
    children: [
      { path: '', component: ContactsCmp },
      { path: ':id', component: ContactCmp }
    ]
  }
];
@NgModule({
 //…
 bootstrap: [MailAppCmp],
 imports: [RouterModule.forRoot(ROUTES)]
})
class MailModule {}
platformBrowserDynamic().bootstrapModule(MailModule);

The button showing the contacts UI can look like this:

<button [routerLink]="/contacts">Contacts</button>

In addition, we can also support linking to individual contacts, as follows:

<a [routerLink]="['/contacts', id]">Show Contact>/a>

In the code sample above all the routes and components are defined together, in the same file(main.bundle.js).

There is one problem with this setup: even though ContactsCmp and ContactCmp are not displayed on load, they are still bundled up with the main part of the application. As a result, the initial bundle is larger than it could have been. A better setup would be to extract the contacts-related code into a separate module and load it on-demand.

Lazy Loading:

We start with extracting all the contacts-related components and routes into a separate file(contacts.bundle.js).

import {NgModule, Component} from ‘@angular/core’;
import {RouterModule} from ‘@angular/router’;
@Component({…}) class ContactsComponent {}
@Component({…}) class ContactComponent {}
const ROUTES = [
  { path: '', component: ContactsComponent },
  { path: ':id', component: ContactComponent }
];
@NgModule({
  imports: [RouterModule.forChild(ROUTES)]
})
class ContactsModule {}

In Angular 2, an ng module is part of an application that can be bundled and loaded independently. So we have defined one in the code above.
Here, we used RoterModule.forChild() instead of RoterModule.forRoot(). This is because in an Angular2 application, only one RoterModule.forRoot() can be defined, and it must be defined by bootstrapping module. All the other modules must use RoterModule.forChild() method.

Referring to Lazily-Loaded Module:

Now, after extracting the contacts module, we need to update the main module to refer to the newly extracted one.

const ROUTES = [
  {
    path: 'conversations/:folder',
    children: [
      {
        path: '',
        component: ConversationsCmp
      },
      {
        path: ':id',
        component: ConversationCmp,
        children: […]
      }
    ]
  },
  {
    path: 'contacts',
    loadChildren: 'contacts.bundle.js',
  }
];
@NgModule({
 //..
 bootstrap: [MailAppCmp],
 imports: [RouterModule.forRoot(ROUTES)]
})
class MailModule {}
platformBrowserDynamic().bootstrapModule(MailModule);

The loadChildren property tells the router to fetch the ‘contacts.bundle.js’ when and only when the user navigates to ‘contacts’, then merge the two router configurations, and, finally, activate the needed components. The bootstrap loads just the main bundle(main.bundle.js). The router won’t load the contacts bundle until it is needed.
Note that apart from the router configuration we don’t have to change anything in the application after splitting it into multiple bundles: existing links and navigations remain unchanged.