Angular Network Aware Preload Strategy
Lazy loading routes
As an app grows, with modules grouping feature components, a common
approach is to lazy load each module from the main routing
configuration. Angular recommends this from the start, because it's a
simple, scalable way to load a module's dependencies only when its route
is activated. You've probably seen or written this in
app-routing.module.ts:
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, from Angular 8:
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)
}
];The second form uses dynamic import(), which is widely
supported.
Either way, each module loads when someone first visits its route.
Preloading everything
The standard way to preload these modules is the
PreloadAllModules strategy. It loads every module in the
routes array in the background, so there's no delay between
a user clicking a route such as /users and its module
downloading.
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 })
]Skipped modules aren't lost. They load 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.