đ Angular Network Aware Preload Strategy
Lazy Loading Routes
As your app becomes larger, with different modules to group feature
components, a common approach is to lazy load each module in your main
routing configuration. Angular recommends this from the start, since
itâs a simple yet scalable pattern to load your projectâs dependencies
once the route is activated. You have likely seen or
implemented this pattern in the app-routing.module.ts like
below:
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
const routes: Routes = [
{
path: 'index',
loadChildren: 'src/app/modules/home/home.module#HomeModule'
},
{
path: 'shop',
loadChildren: 'src/app/modules/shop/shop.module#ShopModule'
}
];or
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
const routes: Routes = [
{
path: 'index',
loadChildren: () => import('src/app/modules/home/home.module').then(m => m.HomeModule)
},
{
path: 'users',
loadChildren: () => import('src/app/modules/user/user.module').then(m => m.UserModule)
}
];Note: the second example is new as of Angular 8, and is a widely supported method of importing modules.
The above examples allow the module to be loaded in when theyâre
requested via accessing the route tied to the module.
Eager Loading
The standard method of preloading any specified modules as above is
to use the PreloadAllModules strategy. This quite simply
loads all modules that have been specified in our routes
array. The advantage of this method is a better user experience since
there isnât any time lag between an end user clicking a route
e.g. /users and the module having to be downloaded.
imports: [
... // omitted for brevity
RouterModule.forRoot(routes,
{ preloadingStrategy: PreloadAllModules })
]Network Aware Preloading
PreloadAllModules is great on a fast connection, but not
everyone is on one. A user on a slow mobile connection, or with data
saver switched on, will have every module downloaded in the background
whether they ever visit those routes or not, competing with the
bandwidth the current page needs.
Angular lets us decide this ourselves by providing a custom
PreloadingStrategy. It has a single method,
preload, which the router calls for each route that uses
loadChildren. Returning load() preloads the
module, returning of(null) skips it.
To decide, we can use the Network Information API.
navigator.connection exposes effectiveType
('slow-2g', '2g', '3g' or
'4g') and saveData, which is true when the
user has asked their browser to use less data.
import { Injectable } from '@angular/core';
import { PreloadingStrategy, Route } from '@angular/router';
import { Observable, of } from 'rxjs';
@Injectable({ providedIn: 'root' })
export class NetworkAwarePreloadStrategy implements PreloadingStrategy {
preload(route: Route, load: () => Observable<any>): Observable<any> {
return this.hasGoodConnection() ? load() : of(null);
}
private hasGoodConnection(): boolean {
const connection = (navigator as any).connection;
if (!connection) {
return true; // API not supported, behave like PreloadAllModules
}
if (connection.saveData) {
return false;
}
return !['slow-2g', '2g'].includes(connection.effectiveType);
}
}Then swap it in for PreloadAllModules:
imports: [
... // omitted for brevity
RouterModule.forRoot(routes,
{ preloadingStrategy: NetworkAwarePreloadStrategy })
]Modules we skip arenât lost, theyâre simply loaded on demand when the user visits the route, exactly as in the lazy loading examples above.
Browser Support
At the time of writing, navigator.connection is only
available in Chromium based browsers. Thatâs why the strategy falls back
to preloading everything when the API is missing, so those users get the
same behaviour as PreloadAllModules. Treat this as a
progressive enhancement rather than something to depend on.