Lazy loading is a technique where you defer loading of content until such a time it’s needed. In single page apps (SPA), usually the whole web app is loaded initially before rendering can even begin. This includes sections of the web app that a user doesn’t need immediately or might never need.

The time it takes to load and render the initial view is usually high especially on slower connections. This is made worse if your app is very large. You can reduce the time it takes to load the initial view by breaking down your applications into smaller chunks, then loading them on demand i.e. Lazy Loading.

Introduction

Let’s take this scenario – If you have an app with three sections: Admin, Customer and Public sections. It’s very rare that visitors using the public section are interested in the admin or customer section. The components,  modules and content that are part of those two sections are more content the browser needs to download before it can render your app. The visitor might not even need those sections and you wasted their time and possibly data. It makes sense to break down your application into at least 3 chunks and load them when needed.

So, if your application is over let’s say 2MB, and if the chunks are ideally equal in size, they end up being about 600 – 700 KBS for each section which ideally increases your loading times by about 66%. This can be further improved by breaking it down into smaller chunks for the individual section or lazy loading the content in the individual pages such as images and articles. This is entirely up to you as the developer.

Before we can continue, you need to have a good grasp on the basics of angular and routing in angular. You can learn the basics of angular routing here.

Getting Started with Lazy Loading

Lazy loading works with angular route module, which supports it out of the box. If you are creating a new application, use the --routing flag to automatically add routing module to your app.

ng new application-name --routing

NB: For Angular 7 and above, you can drop the --routing flag, and angular CLI will prompt you whether you want to enable Routing for your new project.

This will generate a routing file (app-routing.module.ts) next to the app.module.ts. Inside the routing file, there will be an empty variable routes, where you place your routes.

Next, you need to create a feature module (lazy loaded module) for each section of your app you want to lazy load. Ideally, they should be place at the root of the directory where all the components of that sections are placed.

In our case, if we structured our application in such a way that all components for the admin section are in the admin directory, then the feature module for admin, should be placed inside the directory at the root (admin/admin.module.ts and admin/admin-routing.module.ts). To generate such a module, you use the ng generate (g) module module-name command with --routingand --flat options.

This generates a new module with a routing module at the root of the given directory. Learn more about ng generate for modules here.

In our case, our command will look like this:

ng generate module admin/admin --flat --routing

You can do the same for each of our other feature modules (I will refer to them as lazy loaded modules from now onwards) – guest and customer:

ng generate module customer/customer --flat –routing

ng generate module guest/guest --flat –routing

Adding Routes

Next, you need to instruct the main module router, to load lazily the module modules you have just created. To do this, we need to provide path and loadChildren keys for each of our lazy loaded module in our route object.

After Angular 8

Before the release of Angular 8, angular used a static custom string to import lazy loaded modules. This made it harder for Typescript and Linters to catch incorrect path or module until runtime. This changed with the release of Angular 8, which now uses Dynamic imports method to import Lazy Loaded Modules.

[
  {
    path: 'admin',
    loadChildren: () => import('./admin/admin.module').then(m => m.AdminModule) // dynamic import
  }
];

NB: If you use ng update to update Angular to version 8, Angular will automatically convert the import paths from static custom string to dynamic import.

Before Angular 8

[
  {
    path: 'admin',
    loadChildren: './admin/admin.module#AdminModule'
  }
];

You must provide the path to the module followed by the module name, separated by a hash under the loadChildren key. This should be done for all lazily loaded module.

[
  {
    path: 'guest',
    loadChildren: './guest/guest.module#GuestModule', // For Angular 7 and Below
    loadChildren: () => import('./guest/Guest.module').then(m => m.GuestModule) // For Angular 8, using dynamic import method
  },
  {
    path: 'customer',
    loadChildren: './customer/customer.module#CustomerModule', // For angular 7 and Below
    loadChildren: () => import('./customer/customer.module').then(m => m.CustomerModule) // For Angular 8, using dynamic import method
  }
];

The routes in your feature module should look as your normal routes do. Also, take note that you can mix both lazy loaded routes and your normal routes.

Shared Pipes and Components

Working with lazy loaded modules is not always as straight forward as you would like. For one, you can’t declare a component or a pipe in multiple modules in the same angular application. Also, you can’t use two components or pipes together that are not declared in the same module. These are some of the restrictions that I have come across and there could be more. But don’t worry, there are work around for this issues and others.

If you have a component or a pipe you would like to share across multiple lazy loaded modules, you need to create a shared module. In that module, you must declare the Components and Pipes you need to share and then export them. Then, in the lazy loaded module you want to use the shared components and pipes, you import the shared module and you will have access to all components and pipes exported under the shared module. A shared module is just a normal module without routing. I prefer placing all my shared modules under a single directory.

To generate such a module, use the ng generate module module-name --flat. Then, declare (not import) all the pipes and components and the export them.

NgModule({
  imports: [CommonModule],
  declarations: [CustomComponent1, CustomCompent2, CustomComponent3],
  exports: [CustomComponent1, CustomCompent2, CustomComponent3]
});
export class SharedModule {}

NB: Avoid creating large shared modules unless necessary. If you find yourself doing this, you need to evaluate your lazy loading approach and possibly change it.

Working with Third Party Modules (Packages or Libraries)

This is where you will really rip the benefits of Lazy Loading if you rely heavily on large libraries. For each lazy loaded module, you must import only the needed Modules/Libraries it needs. This can significantly reduce the size of your chunks and reducing loading times of your application. A good example of this is Angular Material, which has a lot of material components that needs to be imported. With lazy loading, you can only import only the material component you need per lazy loaded module.

NB: You can also bundle multiple modules under a single shared module to make it easier to import all these modules – just the same way as we did with shared components and pipes.

Working with Services

Services in Angular are less problematic since there singleton. This means that there is only one instance of a service shared by the whole angular app. As such, you can provide services at the highest possible level of your apps lazy loading hierarchy. This allows all lazy loaded modules below to inherit the instance of your service(s). You can learn more about singleton services here.

Final Thoughts

Lazy loading is just one of the many steps of optimizing an angular application. On top of lazy loading angular modules, you can also lazy load content such as images. You can use 3rd party libraries to achieve this. In the next few weeks, I am going to be covering more steps for optimizing your angular application, to reduce load times and improve the user experience.