Preloading Strategy In Angular

Preloading Strategy In Angular

Another technique to speed up the load time of Angular Apps is to use an Angular Preloading Strategy. Angular Modules are used to create modular apps. When a user requests something for the first time, Angular loads all of the modules. This will cause the app to load slowly because it will have to download all of the modules. We can overcome this issue by loading such modules slowly. Angular helps us to improve the performance of our app by employing a method called PreLoading. Let's have a look at what is Preloader in this article. We'll also learn how to employ preloading methods like NoPreloading and PreloadAllModules that are built into the system. Later, we'll look at how to create a custom Preloading strategy so that we have complete control over what is lazy loaded and what gets preloaded.

What is Angular Preloading Strategy?

In Angular, preloading means asynchronously loading the Lazy loaded Modules in the background as the user interacts with the app. This will assist speed up the app's loading time.

Because Angular apps are modular, we can create apps in chunks of modules. When the user navigates to a route, we can load these modules on the fly. Using the loadChildren property of the router, we must mark the modules to be lazy-loaded.

We can lower the initial download size of the app by lazy loading the modules, making the app load faster. In the case of a large application, this is really handy. However, angular will have to download the module if the user navigates to a lazy loaded section of the app.

How to Enable Preloading

To make use of Preloading, we must first enable Module Lazy Loading. When you define routes as shown below, mark the modules with the loadChildren attribute. Angular will load such modules in a lazy manner.

const routes: Routes = [
  {path: "admin", loadChildren:'./admin/admin.module#AdminModule'},
];

Then, using the preloadingStrategy: PreloadAllModules function, you can activate preloading while registering routes using the forRoot method.

RouterModule.forRoot(routes, {preloadingStrategy: PreloadAllModules}) 

Preloading Strategies

Out of the box, Angular comes with two built-in strategies. PreloadAllModules is one option, whereas NoPreloading is another.

NoPreloading

This will turn off all preloading. This is the default behavior, which means that if you don't specify a preloadingStrategy, angular will assume you don't want preloading.

RouterModule.forRoot(routes,
{
    preloadingStrategy: NoPreloading
}

PreloadAllModules

This technique will preload all of the modules that have been loaded late.

RouterModule.forRoot(routes,
{
   preloadingStrategy: PreloadAllModules
}) 

Custom preloading strategy

PreloadAllModules preloads all modules, which may present a bottleneck if the application has a high number of modules to load.

The more effective method would be
  1. Eagerly At initialization, load the relevant modules. For instance, authentication, core, and shared modules are all examples of modules.
  2. Preload all frequently used modules, albeit this may take some time.
  3. Load the remaining modules slowly.

We need to apply a custom preloading technique to selectively preload a module.

To begin, construct a class that extends the built-in PreloadingStrategy class.

The method preload must be implemented by the class (). This method determines whether or not to preload the module. The following is the method signature:

abstract preload(route: Route, fn: () => Observable<any>): Observable<any>
 
Parameters
 route Route 
 fn () => Observable 
 
Returns
Observable<any>

The active Route is the first parameter. We may use this to get information about the route that is currently being loaded.

The second parameter is the Observable function, which must be returned if this module is to be preloaded. If we don't want to preload the module, we can return Observable of null.

The preload method is demonstrated in the following example, which checks if the route has preload data defined. It will return the load argument if it is defined, which will preload the module. If this is not the case, of(null) is returned, indicating that no preload is required.

preload(route: Route, load: () => Observable<any>): Observable<any> {
  
  if (route.data &amp;&amp; route.data['preload']) {
    console.log('preload called');
    return load();
 } else {
    console.log('no preload');
    return of(null);
  }
}

Example of Custom preloading strategy

You can set a delay before preloading the module in a real application. You can also configure different delays for certain routes.

Take a look at the two options below. We've updated the route with new information. The delays on both routes are different.

const routes: Routes = [
  {path: "admin", loadChildren:'./admin/admin.module#AdminModule',data: { preload: true, delay:5000 }},
  {path: "test", loadChildren:'./test/test.module#TestModule',data: { preload: true, delay:10000 }},
];

The CustomPreloadingStrategy class is as follows:

import { Injectable } from '@angular/core';
import { Observable, of, timer  } from 'rxjs';
import { flatMap } from 'rxjs/operators'
 
import { PreloadingStrategy, Route } from '@angular/router';
 
@Injectable()
export class CustomPreloadingStrategy implements PreloadingStrategy {
 
    preload(route: Route, loadMe: () => Observable<any>): Observable<any> {
    
    if (route.data &amp;&amp; route.data['preload']) {
      var delay:number=route.data['delay']
      console.log('preload called on '+route.path+' delay is '+delay);
      return timer(delay).pipe(
        flatMap( _ => { 
          console.log("Loading now "+ route.path);
          return loadMe() ;
        }));
    } else {
      console.log('no preload for the path '+ route.path);
      return of(null);
    }
  }
 
}

First, we implement the PreloadingStrategy in our class.

@Injectable()
export class CustomPreloadingStrategy implements PreloadingStrategy {

The preload method returns an observable with two arguments: route and an observable.

preload(route: Route, loadMe: () => Observable<any>): Observable<any> {

After that, we examine the route information. We check for delay if the preload is true.

if (route.data && route.data['preload']) {
     var delay:number=route.data['delay']
     console.log('preload called on '+route.path+' delay is '+delay);

Then, using the timer, we return the loadMe() after the set duration. Before that, we write the route being loaded to the console.

return timer(delay).pipe(
	flatMap( _ =? { 
		console.log("Loading now "+ route.path);
		return loadMe() ;
	}));

We return the observable of null if route.data is not defined or preload is false (null).

} else {
  console.log('no preload for the path '+ route.path);
  return of(null);
}

Finally, as indicated below, we must include it in the AppModule.

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
 
import { AppRoutingModule } from './app-routing.module';
 
import { AppComponent } from './app.component';
import { CustomPreloadingStrategy } from './custom-preloading-strategy.service';
 
@NgModule({
  declarations: [
    AppComponent, 
  ],
  imports: [
    BrowserModule,
    AppRoutingModule,
  ],
  providers: [CustomPreloadingStrategy],
  bootstrap: [AppComponent]
})
export class AppModule { }

The following graphic depicts how the admin and test modules are loaded after the delay.

Example Of Preloading Strategy In Angular

You may also double-check it under the Network tab.

Example Of Preloading Strategy In Angular

The following code is the complete source code of the application.

Add the following code under the app-routing.module.ts:

import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { CustomPreloadingStrategy } from './custom-preloading-strategy.service';
 
 
const routes: Routes = [
  {path: "admin", loadChildren:'./admin/admin.module#AdminModule',data: { preload: true, delay:5000 }},
  {path: "test", loadChildren:'./test/test.module#TestModule',data: { preload: true, delay:10000 }},
];
 
 
@NgModule({
  imports: [RouterModule.forRoot(routes, { preloadingStrategy: CustomPreloadingStrategy})],
  exports: [RouterModule]
})
export class AppRoutingModule { }

Add the following code under the app.component.css:

ul {
    list-style-type: none;
    margin: 0;
    padding: 0;
    overflow: hidden;
    background-color: #333333;
}
 
li {
    float: left;
}
 
li a {
    display: block;
    color: white;
    text-align: center;
    padding: 16px;
    text-decoration: none;
}
 
li a:hover {
    background-color: #111111;
}

Add the following code under the app.component.html
<ul>
    <li>
      <a class="navbar-brand" routerlink="">home</a>
    </li>
    <li>
      <a class="navbar-brand" routerlink="/admin/dashboard">Admin</a>
    </li>
    <li>
      <a class="navbar-brand" routerlink="/test">Test</a>
  </li>
  
  </ul>
 
<h1>Lazy loaded module Demo</h1>
 
<router-outlet></router-outlet>

Add the following code under the app.component.ts:

import { Component } from '@angular/core';
 
 
@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  title = 'Module Demo';
}

Add the following code under the app.module.ts:

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
 
import { AppRoutingModule } from './app-routing.module';
 
import { AppComponent } from './app.component';
import { CustomPreloadingStrategy } from './custom-preloading-strategy.service';
 
@NgModule({
  declarations: [
    AppComponent, 
  ],
  imports: [
    BrowserModule,
    AppRoutingModule,
  ],
  providers: [CustomPreloadingStrategy],
  bootstrap: [AppComponent]
})
export class AppModule { }

Add the following code under the custom-preloading-strategy.service.ts:

import { Injectable } from '@angular/core';
import { Observable, of, timer  } from 'rxjs';
import { flatMap } from 'rxjs/operators'
 
import { PreloadingStrategy, Route } from '@angular/router';
 
 
@Injectable()
export class CustomPreloadingStrategy implements PreloadingStrategy {
 
    preload(route: Route, loadMe: () => Observable<any>): Observable<any> {
    
    if (route.data &amp;&amp; route.data['preload']) {
      var delay:number=route.data['delay']
      console.log('preload called on '+route.path+' with a delay of '+delay);
      return timer(delay).pipe(
        flatMap( _ => { 
          console.log("Loading now "+ route.path+' module');
          return loadMe() ;
        }));
    } else {
      console.log('no preload for the path '+ route.path);
      return of(null);
    }
  }

Add the following code under the admin/admin.module.ts:

import { NgModule } from '@angular/core';
 
import { AdminRoutingModule } from './admin.routing.module';
import { DashboardComponent } from './dashboard.component';
 
 
@NgModule({
  declarations: [DashboardComponent],
  imports: [
    AdminRoutingModule,
  ],
  providers: [],
})
export class AdminModule { }

Add the following code under the admin/admin.routing.module.ts:

import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
 
import { DashboardComponent } from './dashboard.component';
 
 
 
const routes: Routes = [
    { path: 'dashboard', component: DashboardComponent},
    { path: '', redirectTo:'dashboard'}
];
 
 
@NgModule({
  imports: [RouterModule.forChild(routes)],
  exports: [RouterModule]
})
export class AdminRoutingModule { }

Add the following code under the admin/dashboard.component.ts:

import { Component } from '@angular/core';

@Component({
  template: `<h1>Dashboard Component</h1>`,
})
export class DashboardComponent {
  title = '';
}

test/test.module.ts
import { NgModule } from '@angular/core';
 
import { TestRoutingModule } from './test.routing.module';
import { TestComponent } from './test.component';
 
 
@NgModule({
  declarations: [TestComponent],
  imports: [
    TestRoutingModule,
  ],
  providers: [],
})
export class TestModule { }

Add the following code under the test/test.routing.module.ts:

import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
 
import { TestComponent  } from './test.component';
 
 
const routes: Routes = [
    { path: 'list', component: TestComponent},
    { path: '', redirectTo:'list'},
];
 
 
@NgModule({
  imports: [RouterModule.forChild(routes)],
  exports: [RouterModule]
})
export class TestRoutingModule { }

Add the following code under the test/test.component.ts:

import { Component } from '@angular/core';

@Component({
  template: `<h1>Test Component</h1>`,
})
export class TestComponent {
  title = '';
}

Conclusion

In this article, we learned what is Preloader and how to employ preloading methods like NoPreloading and PreloadAllModules that are built into the system.

I hope this article helps you and you will like it.👍

If you have any doubt or confusion then free to ask in the comment section.

Post a Comment

Previous Post Next Post