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
NoPreloading
RouterModule.forRoot(routes,
{
preloadingStrategy: NoPreloading
}
PreloadAllModules
RouterModule.forRoot(routes, { preloadingStrategy: PreloadAllModules })
Custom preloading strategy
- Eagerly At initialization, load the relevant modules. For instance, authentication, core, and shared modules are all examples of modules.
- Preload all frequently used modules, albeit this may take some time.
- Load the remaining modules slowly.
abstract preload(route: Route, fn: () => Observable<any>): Observable<any> Parameters route Route fn () => Observable Returns Observable<any>
preload(route: Route, load: () => Observable<any>): Observable<any> { if (route.data && route.data['preload']) { console.log('preload called'); return load(); } else { console.log('no preload'); return of(null); } }
Example of Custom preloading strategy
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 && 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 { }
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 && 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 = ''; }