Since Angular is built with the focus on mobile, we put a lot of effort in making lazy loading as easy as possible, so that the initially-loaded application is small.

In this article I’ll walk you through a refactoring, where I’ll take a single-bundle application, and split it into multiple bundles that will be loaded on demand.

What is lazy loading?

Lazy loading speeds up the application load time by splitting it into multiple bundles, and loading them on demand, as the user navigates throughout the app. As a result, the initial bundle is much smaller, which improves the bootstrap time.

Moreover, while the user is interacting with the loaded part of the app, the Angular router will preload other parts in the background. So when the user actually navigates there, it is instant.

Example application

Say you have an application with three modules: MenuModule, MessagesModule, and SettingsModule. Every module defines components, services, and routes.

On launch the application displays the main menu. From there the user can navigate to the list of messages by either clicking on the inbox or drafts links. The user can also open individual messages. Finally, the user can go to the settings page and change the number of messages displayed.

Routes

The application supports the following URLs/routes:

Menu Module / Messages Module /messages/:folder /messages/:folder/:id Settings Module /settings /settings/pagesize

Source Code

You can find the source code of this app in this repository.

Single Bundle

We start with the whole application packaged as a single bundle. Here is its main module.

And the three imported modules look as follows:

As you can see, the main module imports MenuModule, MessagesModule, and SettingsModule. And that’s why all of them have to be bundled together.

Enabling Lazy-Loading

Say you want to refactor the application to lazy load the settings module. To do that change its definition to import RouterModule.forChild instead of RouterModule, like this:

Importing RouterModule adds router directives to the compilation context of the settings module. This means that SettingsCmp and PageSizeCmp can use these directives in their templates. Importing RouterModule.forChild also does that, but, in addition, it registers an array of routes, which will be extracted by the router once the module is loaded.

Next, update the main module to remove all the references to the settings module.

Finally, replace the children property in the router configuration with loadChildren.

And that’s everything you had to do! There are only four lines of code you had to change, and the changes were trivial.

How does loadChildren work?

Before we go any further, let’s look a bit closer at loadChildren. From the router’s perspective, the loadChildren value is opaque — the router will just pass it to a registered module loader. The module loader will fetch the module, extract the ng module factory, and hand it to the router. With this setup you can use the router and lazy loading with practically any module loader: SystemJS, Webpack, etc.

The application we are refactoring here uses the ‘@ngtools/webpack’ plugin. This plugin, at build time, will inspect the router configurations of this app to generate bundles based on them. It’ll also map loadChildren values (e.g., ‘./settings/index’) to their corresponding bundles.

So when the router asks for the settings module factory, the webpack plugin will use the map to fetch the right bundle, extract the factory, and hand it to the router. And the plugin will do it completely transparently without the developer having to configure anything at all.

Lazy Loading Messages Module

Back to the refactoring. Similarly to settings module, update the application to lazy load the messages module.

Now, let’s check that it actually works. Clone the [repository](LINK) and run the following commands:

webpack -p ls -la dist

You’ll see the following three files: bundle.js, 0.bundle.js, 1.bundle.js.

Next, run “npm start”, open the network tab in devtools and navigate around the app. You’ll see that the bundles are loaded only when needed. Here we go! It’s lazy loading in action.

Transparent Lazy Loading

Note that you didn’t have to change the webpack configuration, nor you had to change any components. And that’s why we call our lazy loading transparent: you can opt in and opt out of it with ease. You can change your bundling strategy in minutes or even seconds.

We have put a lot of effort to make sure it is that easy.

First, the router does not require any configuration to generate links. This means that the router can set anchors’ href attributes synchronously, without loading the configurations from any bundles. Only when the user actually clicks on a link, the router will load all the needed configurations to perform the navigation.

Second, switching between lazy and eager loading does not change any behavior. Dependency injection, change detection — everything works exactly the same way regardless of the application’s bundling strategy.

Third, the Angular framework in general, and the router in particular explicitly separate statically-known values from runtime values. That’s why the webpack plugin can do the bundling and the mapping without you having to configure anything at all.

Learn More

If you want to learn more about the Angular router and lazy loading, check out the book I’ve written on the subject: https://leanpub.com/router. You can also read the following articles: