Angular Icon Get 73% off the Angular Master bundle

See the bundle then add to cart and your discount is applied.

0 days
00 hours
00 mins
00 secs

Write Angular like a pro. Angular Icon

Follow the ultimate Angular roadmap.

Lazy-Loading Angular Modules (Ivy and Async Await)

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!

Learn Angular the right way.

The most complete guide to learning Angular ever built.
Trusted by 82,951 students.

Todd Motto

with Todd Motto

Google Developer Expert icon Google Developer Expert

Related blogs 🚀

Free eBooks:

Angular Directives In-Depth eBook Cover

JavaScript Array Methods eBook Cover

NestJS Build a RESTful CRUD API eBook Cover