Skip to content
corpus.web

Angular Router — Lazy Loading Modules

Lazy loading defers a feature's code until someone navigates to it, so the initial bundle carries only what the first screen needs

Baseline Angular 22.1.1
Kind Concept
View source

Continuing from Angular Router in the router configuration article, you already know how to split routing into feature modules. Your code is nicely isolated — if you need to reuse a module, you can copy that chunk into another Angular app and import it into AppModule. We'll keep using the article-list app from the previous lesson, but now we'll add an /admin area for managing articles. That code lives in AdminModule.

A regular user who is not an admin will never need to open the admin page to manage articles (unless a hacker is poking around — but let's not go there). Say your deployed JS bundle is about 1 MB. On a slow connection, loading that file can take several seconds and leave users waiting for no good reason. If you import both ArticleModule and AdminModule into AppModule, both chunks load when the user opens any page. In theory, when someone visits the home page to read articles, the app only needs ArticleModule.

AdminModule should load only when the user clicks the admin link and navigates to /admin. That's what the Router's lazy-loading modules are for. Let's see how it works.

Day 28 recap: feature module

Quick refresher: in the router configuration article, we configured a feature module called ArticleModule:

ts
@NgModule({  imports: [CommonModule, ArticleRoutingModule],  declarations: [    ArticleComponent,    ArticleListComponent,    ArticleDetailComponent,  ],})export class ArticleModule {}
ts
const routes: Routes = [  {    path: 'article',    component: ArticleComponent,    children: [      {        path: '',        component: ArticleListComponent,      },      {        path: ':slug',        component: ArticleDetailComponent,      },    ],  },];
@NgModule({  imports: [CommonModule, RouterModule.forChild(routes)],  declarations: [],  exports: [RouterModule],})export class ArticleRoutingModule {}

Finally, import that module into AppModule:

ts
@NgModule({  imports: [BrowserModule, FormsModule, ArticleModule, AppRoutingModule],  declarations: [AppComponent],  bootstrap: [AppComponent],})export class AppModule {}

Now run npm run build and inspect the JS bundle with source-map-explorer. There are other tools — webpack-bundle-analyzer is popular and more visual — but source-map-explorer is what I'm used to. Install it globally with npm i -g source-map-explorer if you don't have it yet.

After npm run build, open the dist folder and analyze main.js with source-map-explorer main.js. That's the main bundle loaded when the page opens.

Step 1
Step 1

You'll see what's inside main.js. When it loads, both AppModule and ArticleModule come along — exactly what we expect.

Step 2
Step 2

Adding another feature module

Next we'll add AdminModule: articles in a table with edit/delete buttons. This is a demo, so the buttons are placeholders — we won't implement those actions.

The UI will look roughly like this:

Step 3
Step 3

Let's code. We'll create AdminModule with two components:

  • AdminComponent — layout shell, like ArticleComponent
  • AdminArticleListComponent — table view for admin tasks

AdminRoutingModule:

ts
const routes: Routes = [  {    path: 'admin',    component: AdminComponent,    children: [      {        path: '',        component: AdminArticleListComponent,      },    ],  },];
@NgModule({  imports: [RouterModule.forChild(routes)],  exports: [RouterModule],})export class AdminRoutingModule {}

Because we'll render a table and edit articles, we also import ReactiveFormsModule in AdminModule:

ts
@NgModule({  declarations: [AdminComponent, AdminArticleListComponent],  imports: [    CommonModule,    ReactiveFormsModule, // <-- Import for forms later    AdminRoutingModule,  ],})export class AdminModule {}

Import AdminRoutingModule into AdminModule, then import AdminModule into AppModule:

ts
@NgModule({  imports: [BrowserModule, AdminModule, ArticleModule, AppRoutingModule],  declarations: [AppComponent],  bootstrap: [AppComponent],})export class AppModule {}

Run npm start and you'll see the UI from the animation above. The interesting part is the JS bundle again. With source-map-explorer on main.js, AdminModule is now included. Where did Angular Core and ReactiveFormsModule go? In vendor.js. Run source-map-explorer vendor.js:

Step 4
Step 4

Forms live there — the red highlighted section.

Step 5
Step 5

Takeaway: with feature modules, everything you use across all feature modules still loads when the app starts. Fine for small apps; painful for large ones and user experience.

Now for the main topic — lazy loading. Once it works, AdminModule and its ReactiveFormsModule dependency load only when you open /admin.

Lazy load module

To lazy-load a module in Angular:

  1. Remove AdminModule from AppModule's imports array. Importing it there bundles AdminModule with AppModule, as we saw above. Note: remove the top-level import statement too, or nothing changes.

Step 6
Step 6

  1. Adjust routes in AdminModule. Drop the admin path segment from AdminRoutingModule — step 3 below shows why.

Step 7
Step 7

  1. In AppRoutingModule, configure lazy loading for the /admin path.

Step 8
Step 8

The path is still admin, but instead of a component, use loadChildren with () => import('./admin/admin.module').then((m) => m.AdminModule) — a function that returns a dynamic import() of the module.

Notice that the lazy-loading syntax uses loadChildren followed by a function that uses the browser's built-in import('...') syntax for dynamic imports. The import path is the relative path to the module.

Done. Run npm run build again. The CLI now emits a separate chunk for AdminModule: admin-admin-module.js.

Step 9
Step 9

Open the app and watch the network tab:

Step 10
Step 10

On first load, the app fetches the usual files and main.js. When you click Admin and go to /admin, then admin-admin-module.js loads. That's lazy loading — not upfront, only when needed.

Analyze both bundles:

source-map-explorer main.jssource-map-explorer admin-admin-module.js

Step 11
Step 11

main.js no longer contains AdminModule — only ArticleModule.

Step 12
Step 12

And here's admin-admin-module.js — Forms and admin components live in this chunk. Excellent.

Lazy load syntax

The import('...') syntax is recommended from Angular 8 onward.

Before that (Angular 7 and below), you could write loadChildren: './admin/admin.module#AdminModule' — a magic string pointing at the NgModule file that exports the module with routing.

Preloading lazy modules

Splitting lazy modules helps the first page load: smaller JS means faster time-to-interactive. But a lazy chunk can still be large, so clicking a link may stall while it downloads. Even our small AdminModule example can feel slow on a throttled mobile network:

Step 13
Step 13

Ten seconds from click to UI is rough.

Some modules are almost always visited right after launch. Preloading lets you fetch those chunks in the background.

To preload all lazy-loaded modules, import PreloadAllModules from @angular/router and pass it to RouterModule.forRoot:

ts
import { PreloadAllModules } from '@angular/router';
const routes: Routes = [  {    path: 'admin',    loadChildren: () =>      import('./admin/admin.module').then((m) => m.AdminModule),  },  {    path: '',    redirectTo: 'article',    pathMatch: 'full',  },];
@NgModule({  imports: [    RouterModule.forRoot(routes, {      preloadingStrategy: PreloadAllModules,    }),  ],  exports: [RouterModule],})export class AppRoutingModule {}

Run the app again:

Step 14
Step 14

Right after the home page loads, admin-admin-module.js downloads automatically. I didn't use ng build with --prod=true here, so the file is chunky; production builds are much smaller.

For selective preloading instead of all lazy routes, see route preloading in Angular. We won't cover custom strategies in this article — follow that link if you need them.

Summary

You should now see why lazy loading matters and how to configure a lazy-loaded route in Angular Router. For practice, try converting ArticleModule to lazy loading as well.

Code example

https://stackblitz.com/edit/angular-100-days-of-code-day-29-router-lazy

Youtube Video

Day 29
Day 29

References

0%