Angular — Faster performance and better user experience with Lazy Loading

What is Lazy Loading ?

Lazy loading is a design pattern commonly used in computer programming to defer initialization of an object until the point at which it is needed. It can contribute to efficiency in the program’s operation if properly and appropriately used. (source: wikipedia)

In simple terms for our context,

don’t load something which you don’t need.

How does lazy loading of Routes improve performance in Angular app?

A large scale application will contain lots of feature modules which need not be loaded all at once. The feature modules can be loaded asynchronously after the app has loaded either on demand or using different strategies (will be discussed later). Reducing the size of bundle when the app loads initially improves the app load times thus improving user experience.

I have built a simple Gmail clone using random data for this blog post. I have not optimized the code in any manner this way I can increase the total bundle sizes and few routes/modules to explain the concept.

The app is a straight forward app, it has one routing file app.routing.ts and all the features has been split into modules. I am using the Augury extension for debugging Angular application to inspect our app routes. Installation instruction are on augury.angular.io.

After installation of Augury, open up dev tools and navigate to Augury tab then to Router Tree. Below is the router tree for this app.

You can see above we have 2 levels of nested routing and each level contains multiple routes. In real world apps you might have more than this and each of the module might contain multiple components, templates etc., The user will not need all these code on the initial load.

Refactoring our app to lazy load routes

I am going to focus on lazy loading settings module and routes within that module.

Step#1

Create feature module (settings module) route registration. Currently all routes are registered in app.routing.ts and then imported into app.module.ts. Below is our current routing file.

Move routing for settings to its own module gm-settings.module.ts. When registering the routes with the RouterModule we need to use forChild instead of forRoot as this is child routing system for our app. So gm-settins.module.ts will look something like below.

Step#2

Update the app.routing.ts to load the child routes using relative path to the settings module and appending the module class name

Step#3

Settings module is currently imported into our app.module.ts, we need to remove that reference as Angular will dynamically configure the registration of Settings module.

So the only reference to settings module is the relative path string we added to app.routing.ts. Lets look at our router tree, you can see the Lazy appended to settings module.

If we look at the network tab, we can see an additional file 5.chunk.js getting loaded on click on the setting icon which has GmSettingsModule. The routing registration gets activated once the user navigates to the setting route.

I have done the same methodology for email route as well. Taking a look at the network tab we can see two chunks are loaded (settings and email) which leads us to the next section of preloading strategy.

Preload strategy

The module (chunks) file gets loaded when the user clicks on settings or particular email. So over the network this might cause delay for the user resulting in bad user experience.

For this scenario, Angular provides a method to tell the router to load all the lazy loading modules asynchronously immediately after the app loads and not to wait for the module to get activated when the user clicks.

In the above gif, we can see all the *.chunk.js get loaded immediately after the app is initialized.

Custom Preload Strategy

The above changes solves two problems,

  1. It improves the app load times by reducing the initial load size
  2. It preloads the chunks so that there is no delay for the user when navigated to any lazy loaded module.

But, this introduces another problem. We might be downloading the chunks which the user might very rarely navigate to. Finding the right balance of performance and user experience is key in development.

Example: In our application, we have two lazy loaded modules

  1. Settings module
  2. Email module

Our app is an email client so the email module will be used very very often. But settings module will be used by the user but on very low number of occasions. There is value to preload the email module but there is less value to preload the settings module. (on large scale app you might have many modules which need not be preloaded)

Angular provides away to extend PreloadingStrategy to specify our own Custom strategy to indicate under which conditions to preload a lazy loaded module. We will create a provider extending PreloadStrategy to preload modules which has preload:true specified in route configuration.

CustomPreloadingStrategy should be registered to providers in the module where the RouterModule.forRoot is declared. In app.module.ts,

In app.routing.ts,

Taking a look at the network tab, we can 5.chunks.js (email module)is preloaded and 6.chunks.js is loaded async on navigation to (settings module).

This post is kind of longer than usual. The concept of the lazy loading with strategic implementation of conditional preloading can improve application performance and improve user experience drastically.

Link to Github Repo . It has two branches

  1. master : non lazy loading app
  2. lazy-loading: lazy loading implemented for settings and email.

If you liked this post, please share, comment and press the 💚 .