DEV Community

Cover image for Working With Lazy Loading Modules and Preload Routing Strategies In Angular
Dany Paredes for This is Angular

Posted on • Originally published at danywalls.com

Working With Lazy Loading Modules and Preload Routing Strategies In Angular

When we build an Angular application with multiple modules in a large app, the main script file becomes a giant monster. One alternative to improve the user experience is to use the Lazy Module. Today, We will learn how to use lazy loading to enhance the user experience and add preload and custom strategies for loading modules.

The Scenario

I'm working on a payment app with four modules Dashboard, MoneyTransfer, Wallet and Activity. Each one represents a business context with components. The following example shows the ActivityModule is the same for every module.

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { ActivityComponent } from './activity.component';
import { ActivityRouterModule } from './activity.routing.module';

@NgModule({
  imports: [
    CommonModule
  ],
  declarations: [ActivityComponent]
})
export class ActivityModule { }

Enter fullscreen mode Exit fullscreen mode

Import every module in the app.module and register in the imports section. We need to configure the Router module to navigate to every component.

The RouterModule.forRoot use a route configuration with the path and the component to load in the <router-outlet></router-outlet>.

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppComponent } from './app.component';
import { NotFoundComponent } from './not-found/not-found.component';
import {MoneyTransferModule} from "./moneytransfer/moneytransfer.module";
import {DashboardModule} from "./dashboard/dashboard.module";
import {WalletModule} from "./wallet/wallet.module";
import {ActivityModule} from "./activity/activity.module";
import {RouterModule, Routes} from "@angular/router";
import {WalletComponent} from "./wallet/wallet.component";
import {ActivityComponent} from "./activity/activity.component";
import {DashboardComponent} from "./dashboard/dashboard.component";
import {MoneyTransferComponent} from "./moneytransfer/moneytransfer.component";

const routes: Routes = [
    { path: '', redirectTo: '/dashboard', pathMatch: 'full' },
    { path: 'wallet', component: WalletComponent },
    { path: 'activity', component: ActivityComponent },
    { path: 'dashboard', component: DashboardComponent },
    { path: 'money', component: MoneyTransferComponent },
    { path: '**', component: NotFoundComponent },
]
@NgModule({
  declarations: [AppComponent, NotFoundComponent],
  imports: [BrowserModule, MoneyTransferModule, DashboardModule, WalletModule, ActivityModule,
    RouterModule.forRoot(routes)
  ],
  bootstrap: [AppComponent],
})
export class AppModule {}

Enter fullscreen mode Exit fullscreen mode

The app is ready to use. Let's see the final output.

Learn more about Routing in Angular

The Scenario

What is Angular doing?

Every module is compiled, merged, packaged, and bundled in the main.js file. We are loading all modules in a single file, it hits the application load time, and the user needs to wait for all modules to interact with the application.

Some questions come to my head.

  • We are loading 36.0kB in the Dashboard area.
  • Why load all modules?
  • Why do we need to wait to load other modules that are not working?

We are going to improve the situation using Angular Lazy Loading Modules.

Lazy Loading

Let's start splitting the responsibility and separate importing all modules from the app.module. Create a new file app.routing.module, responsible for loading the modules.

The app.routing.module, help us to define the routes and load the modules, using the'loadChildren' property in the route definition.

The loadChildren import module when the user requests the routes and loads the module.

const routes: Routes = [
  { path: '', redirectTo: '/dashboard', pathMatch: 'full' },

  {
    path: 'wallet',
    loadChildren: () =>
      import('./wallet/wallet.module').then((m) => m.WalletModule),
  },
  {
    path: 'activity',
    loadChildren: () =>
      import('./activity/activity.module').then((m) => m.ActivityModule),
  },
  {
    path: 'dashboard',
    loadChildren: () =>
      import('./dashboard/dashboard.module').then((m) => m.DashboardModule),
  },
  {
    path: 'money',
    loadChildren: () =>
      import('./moneytransfer/moneytransfer.module').then(
        (m) => m.MoneyTransferModule
      ),
  },
  { path: '**', component: NotFoundComponent },
];

@NgModule({
  imports: [
    RouterModule.forRoot(routes)
  ],
  exports: [RouterModule],
})
export class AppRoutingModule {}
Enter fullscreen mode Exit fullscreen mode

Next, we need to configure the modules to expose a RouterModule configuration to load the component using the RouterModule.forChild() method.

The process is the same for every module, we show the example with the ActivityModule

The ActivityRouterModule imports the RouterModule and configures the RouterModule.forChild() with the routes object with the path and the component to load.

The ActivityRouterModule

import { NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';
import { ActivityComponent } from './activity.component';

const routes = [  { path: 'activity', component: ActivityComponent }]

@NgModule({
  imports: [ RouterModule.forChild(routes) ],
  exports: [ RouterModule],
})
export class ActivityRouterModule { }
Enter fullscreen mode Exit fullscreen mode

The 'ActivityModule' imports the ActivityRouterModule to provide the routing configuration.

We remove all module references from the app.modules and only need to register the app.routing.module.

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppComponent } from './app.component';
import { NotFoundComponent } from './not-found/not-found.component';
import {AppRoutingModule} from "./app-routing.module";

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

Enter fullscreen mode Exit fullscreen mode

Perfect! We improve the load, only getting the minimum files and every module load when the user navigates the path.

Lazy Loading

What do we get?

  • The size of main.js is decreased from 32KB to 12.8 KB
  • The modules are loaded on demand.

Side effects

One of the advantages is our new pain, "the loading on demand"; when the user clicks on the module, it takes time to request and load the module, getting delayed.

Side effects

PreLoad Modules

Angular help us to one once the app is loaded, fetch all the remaining module chunks, and have faster navigation between different modules using preloadingStrategy.

The preloadingStrategy helps us to download the modules asynchronously once you add the configuration in the root module routing.

The router in the forRoot method passes an object with the option preloadingStrategy: PreloadAllModules.

The PreloadAllModules come from the angular router

@NgModule({
  imports: [
    RouterModule.forRoot(routes, {
      preloadingStrategy: PreloadAllModules,
    }),
  ],
  exports: [RouterModule],
})
export class AppRoutingModule {}

Enter fullscreen mode Exit fullscreen mode

Now, we have the lazy loading and downloading of the remaining modules asynchrono

PreloadAllModules

Read more about PreLoadStrategy https://angular.io/api/router/PreloadingStrategy

Custom Loading Strategy

The PreloadAllModules loads all modules. Maybe we want to control which modules load, and maybe we want to control or be selective about which modules will use the preloadingStrategy.

We can customize the preloadingStrategy to create a class that implements the built-in PreloadingStrategy interface.

The class must implement the method preload(). In this method, we determine whether to preload the module or not in the route.

We need to activate some flags in the route to know if the route will load or not. The easy way is to use the route.data property to set up the flag.

The load function compares if it contains the preload option to load the modules, then execute the load or returns an empty observable.

import { Injectable } from '@angular/core';
import { PreloadingStrategy, Route } from '@angular/router';
import { Observable, of } from 'rxjs';
@Injectable({
  providedIn: 'root',
})
export class ModuleLoadingStrategyService implements PreloadingStrategy {
  preload(route: Route, load: () => Observable<any>): Observable<any> {
    if (route.data && route.data['preload']) {
      return load();
    }
    return of(null);
  }
}

Enter fullscreen mode Exit fullscreen mode

Change the PreLoadAllModules to ModuleLoadingStrategyServicestrategy.

import {ModuleLoadingStrategyService} from "./config/module.loading.strategy";

@NgModule({
  imports: [
    RouterModule.forRoot(routes, {
        preloadingStrategy: ModuleLoadingStrategyService,
    })
  ],
  exports: [RouterModule],
})
Enter fullscreen mode Exit fullscreen mode

Finally, set the preload property in the router.data for the module to use the custom preload strategy, for example, wallet.

{
    path: 'wallet',
    loadChildren: () =>
      import('./wallet/wallet.module').then((m) => m.WalletModule),
    data: { preload: true },
  },
Enter fullscreen mode Exit fullscreen mode

Custom Loading Strategy

Perfect! We have lazy loading with a custom preload strategy to load specific modules in the app!

Recap

We learn how to implement lazy loading, preload the modules, and create a custom strategy to have a particular module for loading and speeding the application.

Photo by Marcin Simonides on Unsplash

My recommendation use lazy loading with a custom preload strategy. Pick the modules necessary for the application or high demand by the users, and maybe add some delay in the loading time. I hope it helps to speed up the performance of your Angular Apps.

Top comments (0)