In this post you’ll learn how to Lazy Load an Angular module. Lazy loading means that our code isn’t downloaded by the browser until it’s needed.
What is Lazy Loading?
For instance, if I login to /admin
I would get a “chunk” of JavaScript code specifically for the Admin dashboard. Similarly, if I load /shop
I would expect another “chunk” of JavaScript specifically for just the Shop!
If I visited the Admin panel and navigated to Shop, the code related to the Shop would be lazy loaded and “injected” into the browser (in simple terms) for us to use the Shop module.
After compiling and deploying our Angular app, we could have a JS file per lazy loaded module, for example:
main.chunk.js // loaded everywhere
shop.chunk.js // lazy module
admin.chunk.js // lazy module
The best thing is that when we visit /admin
, there is no code downloaded that’s part of our Shop, and vice versa. This keeps bundle sizes small, and code efficiently performant.
With this in mind, let’s learn how to lazy load any module with Angular! I also want to show you a newer approach that uses async await.
Lazy Loading a Module
Back in TypeScript 2.4 the dynamic import syntax was introduced, this became the defacto way of importing TypeScript modules on the fly (so they weren’t bundled with our code).
Angular is built with TypeScript - so what did that mean? Angular had to change its approach to lazy loading a module - as this was done via a “magic string” beforehand - to the new dynamic import syntax (which makes so much more sense)!
Create a Module to Lazy Load
First, we’ll need to create a lazy module in our app, which will use RouterModule.forChild(routes)
to define a child module:
// lazy.module.ts
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { LazyComponent } from './lazy/lazy.component';
const routes: Routes = [
{
path: '',
component: LazyComponent,
},
];
@NgModule({
declarations: [LazyComponent],
imports: [RouterModule.forChild(routes)],
})
export class LazyModule {}
Using path: ''
allows us to define which route we’ll hit for our LazyModule
to be created, we’ll see this momentarily.
At this point, just because our module is “called” lazy, it’s not lazy yet. We’ll need to add this to our root app module, and define the routes we’d like to setup.
Declare the Lazy Load Routes
The way we lazy load Angular modules in v9 and above (with Angular Ivy!) is through the route declarations!
Currently, the Angular documentation suggests a Promise-based syntax for declaring which routes to lazy load, which we add to a loadChildren
property on the routes:
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { AppComponent } from './app.component';
const routes: Routes = [
{
path: 'lazy', // visit `/lazy` to load LazyModule
loadChildren: () => import('./lazy/lazy.module').then(m => m.LazyModule),
},
{ path: '', pathMatch: 'full', redirectTo: 'lazy' },
];
@NgModule({
declarations: [AppComponent],
imports: [BrowserModule, RouterModule.forRoot(routes)],
providers: [],
bootstrap: [AppComponent],
})
export class AppModule {}
But Angular isn’t about Promises, so I find the .then(m => m.LazyModule)
a bit of an eyesore, it just feels wrong in the codebase - can we do better?
Using async await
With the addition of async await in TypeScript, we can use the syntactic sugar to clean things up a little!
Changing over our Promise-based routing implementation to using the newer async await syntax looks far nicer.
We can omit the .then()
altogether and use the await the return value from await import(...)
and reference .LazyModule
directly:
const routes: Routes = [
{
path: 'lazy',
loadChildren: async () => (await import('./lazy/lazy.module')).LazyModule,
},
{ path: '', pathMatch: 'full', redirectTo: 'lazy' },
];
Legacy syntax (Angular v7 and below)
Lazy loading in Angular has come a long way since the beginning, where we used magic string to denote a module to be loaded - just for fun here’s what we used to do:
const routes: Routes = [
{
path: 'lazy',
loadChildren: './lazy/lazy.module#LazyModule',
},
{ path: '', pathMatch: 'full', redirectTo: 'lazy' },
];
You can see how this could easily be prone to spelling errors and the mystical #
to denote the module’s name. Now we just simply reference the object directly which is nicely inferred through TypeScript.
It’s great that Angular Ivy has introduced better techniques for lazy loading that make it even more efficient for us to get setup and code splitting!
If you are serious about your Angular skills, your next step is to take a look at my Angular courses where you’ll learn Angular, TypeScript, RxJS and state management principles from beginning to expert level.
Happy lazy loading!