Lazy loading with Angular

IT-ROCKSTAR ROBIN VAN TIENHOVEN

These days mobile applications can become quite big. A dashboard consisting of 10 components here, another page with 2-6 components there and of course some services providing data for all the separate components.

Each of those components also consists of a HTML page, a stylesheet and the actual logic in a TypeScript file. All of this code is automatically bundled by Webpack, which makes deploying a lot easier. But it also has a downside, the browser needs to download these files when loading the page and it can only start loading your web application when the entire bundle has been downloaded.

Angular and Webpack already offer some nifty out-of-the-box features that help you minimize the size of your bundles, such as code minificationTree shaking and the AOT Compiler (which is automatically activated in dev mode when running Angular 9 or above, to improve the developer experience).

But there are still a few more steps you can take to manually improve the performance of your application. One of these features is offered by the Angular Router, called “Lazy loading”.

Lazy loading Modules

The Angular router manages the flow of your whole application, it knows when it should load a certain page or when a page should be destroyed (or cached). because it knows what page is about to be loaded, it also knows which Module is necessary to get data to display on the page. As you might have guessed, it can serve as much more than a simple page loader.

Because it knows what Modules (and components) need to be loaded in order to display that page, it can split that Module from the main bundle into a smaller separate bundle. This allows your browser to load a few smaller bundles instead of one big bundle.

You might think that having your browser download more files has a negative effect inpact on your application’s performance, but it is quite the opposite. Let me explain why. When the browser needs to download a single file it can only use a single thread. Because of the way the file is served from the server, it can’t be retrieved chunk by chunk, it has to be downloaded all at once. On top of that, the browser can only run your application when the file has finished downloading.

As you can see a single big file can be quite limiting. But what if could serve a lot of small files? This is exactly the point of lazy loading. Angular will bundle your Modules in small JavaScript files, based on the page they are used in. When requesting a set of small files, the browser can use multiple threads to download the files in parallel. This way it can download the files faster, which improves the loading time of your application.

In short
When enabling lazy loading, Angular will bundle Modules in small JavaScript packages, which can be loaded seperately from the main bundle. This enables the browser to use multithreading, thus downloading your application’s files faster.

Creating our demo application

Now that we know the theory behind lazy loading we can look at an actual use case. Let’s rebuild our disco application, but this time we are going to build it using just Angular.

First, we need to create a new project

ng new disco-app --minimal --routing --style=scss

Now that we have created our project we can start programming.

Adding lazy loading

Adding lazy loading to our application is fairly straightforward. All we need to do is change the way we define our routes. But before we can add routes we need a new page to redirect to. So let’s create one (you can add as many colors as you want):

ng generate module yellow
ng generate component yellow

In order to make it a real disco app we need to add some color. So replace the styles array in yellow.component.ts with the following:
styles: [':host { background: yellow; width: 100%; height: 100%; display: inline-block }']

Now that we’ve created our (yellow) color page we can add it to our routing file. Open the src/app/app-routing.module.ts and replace the routes with the following:

const routes: Routes = [
 {
 path: 'yellow',
 loadChildren: () => import('./yellow/yellow.module').then(m => m.YellowModule),
 },
 {
 path: '',
 pathMatch: 'full',
 redirectTo: 'yellow'
 }
]; 

This will load our yellow page on startup. The .then(m => m.YellowModule) will load the actual Module that is connected to the page. This will, however, load just the Module and not the components that are related to that Module. Because of that we need to add a router path to the Module definition, which tells the router to also load the component when the Module is loaded.

Let’s add this path to the Module, go to src/app/yellow/yellow.module.ts and add the following code:

const routes: Routes = [ { path: '', component: YellowComponent }, ]; 

Now that we have defined the route we need to register it as a child route with the router. We can achieve this by calling the RouterModule.forChild method. Add the following code to the imports array of the YellowModule:

RouterModule.forChild(routes), 

And that’s it! If we now fire up our application via ng serve you can see that the yellow page has been loaded. You can optionally replace the contents of the app.component.ts with the following template to make it clearer: template: `<router-outlet></router-outlet>`,.

 

Viewing the result

So the page now loads and everything looks okay, but how can we check whether the lazy loading has an actual effect on the build output? Just run ng build and look at the /dist folder. You will find a separate module file for the yellow Module in there:

A separate file means a separate download.
Mission accomplished!

 

Preloading all the Modules

Lazy loading already improves the performance of the application by splitting up the bundle in multiple smaller bundles. But there is still a catch. The application will still download every resource on app load, even the bundles that are not yet needed. Which still means that we need to wait until everything has finished downloading before we can load the application.

For this the Angular Router has a second trick up its sleeve. We can change this behavior by setting the preloadingStrategy of the Angular Router to PreloadAllModules. This allows the router to delay the download of bundles which aren’t directly required to a period of inactivity after the initial application load. With this, fewer resources have to be loaded upfront, which results in shorter loading times of your application.

Changing the loading strategy is even easier than implementing lazy loading, because it only requires an extra configuration object for the router.

Add the following code to src/app/app-routing.module.ts:

const routerOptions: ExtraOptions = {
 preloadingStrategy: PreloadAllModules,
 }; 

and change the RouterModule import to: RouterModule.forRoot(routes, routerOptions).

That’s it! That is all we need to make it work. If we now look at the network tab in our browser (don’t forget to run ng serve), we can see that the yellow-yellow-module.js bundle is loaded after the initial load!

 

Conclusion

As you can see, there are still a few tricks that you can implement to improve the performance of your application. Some are easier than others, but implementing lazy loading definitely belongs to the easy category. Now with the new Ivy compiler, that automatically ships with Angular 9, you can even lazy load individual components for even more control and performance boosts.

A nice article covering the lazy loading of components can be found here, on Medium.


Thanks to the following Rockstars for proofreading and editing this article:

  • Vincent Spaa
  • Erik Euser
  • Ivar Lugtenburg
  • Robin Gordijn