Der neue Router für Angular erlaubt das verzögerte Laden (Lazy Loading) von Modulen. Auf diese Weise lässt sich die Startgeschwindigkeit einer Angular-basierten SPA optimieren. Das von Angular-Mastermind Victor Savkin auf der AngularConnect 2016 in London vorgestellte Preloading geht darüber hinaus, indem es eine weitere Performanceoptimierungen erlaubt: Es nutzt freie Ressourcen nach dem Start der Anwendung zum Nachladen von Modulen, die später per Lazy Loading angefordert werden könnten. Werden der Router diese Module später tatsächlich benötigt, stehen sie augenblicklich zur Verfügung.
In diesem Beitrag zeige ich, wie Preloading in einer Angular-Anwendung genutzt werden kann. Das gesamte Beispiel findet man hier. Es basiert auf Angular 2.1.0-beta.0 und dem Router 3.1.0-beta.0. Dabei handelt es sich um die ersten Versionen, welche dieses Feature anbieten.
Ausgangssituation
Das hier vorgestellte Beispiel nutzt ein AppModule
, welches per Lazy Loading ein FlugModule
einbindet. Dazu verweist es über loadChildren
auf den Namen des Modules und die Datei, in dem es zu finden ist:
// app.routes.ts
import {Routes, RouterModule} from @angular/router;
import {HomeComponent} from "./modules/home/home/home.component";
const ROUTE_CONFIG: Routes = [
{
path: home,
component: HomeComponent
},
{
path: flug-buchen,
loadChildren: ./modules/flug/flug.module#FlugModule
},
{
path: **,
redirectTo: home
}
];
export const AppRoutesModule = RouterModule.forRoot(ROUTE_CONFIG);
Die abschließende Zeile in diesem Listing erzeugt mit der Routing-Konfiguration eine konfigurierte Variante des RouterModules
und exportiet diese über die Variable AppRoutesModule
. Das AppModule
, welches hier als Root-Module dient, verweist darauf:
// app.module.ts
import {NgModule} from "@angular/core";
import {AppRoutesModule} from "./app.routes";
[...]
@NgModule({
imports: [
BrowserModule,
HttpModule,
FormsModule,
AppRoutesModule,
[...]
],
declarations: [
AppComponent
],
bootstrap: [
AppComponent
]
})
export class AppModule {
}
Damit loadChildren
in der Routen-Konfiguration auf die gezeigte eingängige Weise mit Webpack 2 zusammenspielt, kommt der angular2-router-loader
zum Einsatz. Dieser lässt sich mit npm
beziehen (npm i angular2-router-loader --save-dev
) und dient als zusätzlicher Loader für .ts-Dateien in der webpack.config.js
:
[...]
module: {
loaders: [
[...],
{ test: /\.html$/, loaders: [html-loader] },
{ test: /\.ts$/, loaders: [angular2-router-loader?loader=system, awesome-typescript-loader], exclude: /node_modules/}
]
},
[...]
Der Parameter ?loader=system
veranlasst den Loader dazu, die per Lazy Loading angeforderten Module via System.import
zu laden.
Preloading
Zum Aktivieren von Preloading ist ab Version 3.1.0 des Routers lediglich beim Erzeugen des konfigurierten AppRoutesModule
eine PreloadingStrategie
anzugeben:
import {Routes, RouterModule, PreloadAllModules} from @angular/router;
[...]
export const AppRoutesModule = RouterModule.forRoot(ROUTE_CONFIG, { preloadingStrategy: PreloadAllModules });
Die hier verwendete Strategie PreloadAllModules
führt dazu, dass die Angular-Anwendung dem Programmstart sämtliche Module per Preloading bezieht.
Das Ergebnis dieses Unterfangens lässt sich in Chrome in den F12-Dev-Tools unter Network
beobachten. Da das Laden lokaler Dateien sehr schnell von statten geht, empfiehlt es sich, dabei die Netzwerkgeschwindigkeit zu drosseln. Die nachfolgende Abbildung demonstriert zum Beispiel das Ladeverhalten bei einer simulierten 3G-Verbindung:

Beim Laden der Seite zeigt das betrachtete Fenster, dass Angular das Bundle 0.js
mit dem FlugModule
erst nach dem Start der Anwendung lädt. Da dieses Bundle jedoch recht klein ist, muss man dazu sehr genau schauen. Deswegen beschreibt der nächste Abschnitt ein Experiment, mit dem dieser Umstand besser nachvollzogen werden kann.
Preloading mit Experiment nachvollziehen
Zum besseren Nachvollziehen der Tatsache, dass das Preloading erst nach dem Start der Anwendung beginnt, kommt in diesem Abschnitt eine benutzerdefinierte Preloading-Strategie zum Einsatz. Diese führt mit RxJS eine Verzögerung von ein paar Sekunden aus, bevor sie sich um das Laden des Modules kümmert.
Zum Bereitstellen einer eigenen Preloading-Strategie ist das Interface PreloadingStrategy
zu implementieren:
// custom-preloading-strategy.ts
import {PreloadingStrategy, Route} from "@angular/router";
import {Observable} from rxjs;
export class CustomPreloadingStrategy implements PreloadingStrategy {
preload(route: Route, fn: () => Observable<any>): Observable<any> {
return Observable.of(true).delay(7000).flatMap(_ => fn());
}
}
Die Methode preload
der PreloadingStrategy
erhält Angular die Route, welche es zu laden gilt, sowie eine Funktion, die das Laden übernimmt. Somit kann sie entscheiden, ob die betroffene Route per Preloading bezogen werden soll und diesen Vorgang ggf. auch anstoßen. Das retournierte Observable
informiert Angular, wenn preload
ihre Aufgabe erledigt hat.
Die hier betrachtete Implementierung erzeugt ein Observable mit dem (Dummy-)Wert true
und versendet diesen mit einer Verzögerung von 7 Sekunden. Nach dieser Zeitspanne führt flatMap
das Preloading durch.
Um die CustomPreloadingStrategy
zu verwenden, ist darauf beim Erzeugen des konfigurierte AppRoutesModule
zu verweisen. Da an dieser Stelle die Strategie lediglich als Token zum Einsatz kommt, benötigt Angular zusätzlich einen Provider dafür:
// app.routes.ts
[...]
export const AppRoutesModule = RouterModule.forRoot(ROUTE_CONFIG, { preloadingStrategy: CustomPreloadingStrategy });
export const APP_ROUTES_MODULE_PROVIDER = [CustomPreloadingStrategy];
Damit der Provider der Anwendung zur Verfügung steht, referenziert das AppModule
ihn über ihr Array providers
. Das konfigurierte AppRoutesModule
referenziert es natürlich nach wie vor:
// app.module.ts
import {AppRoutesModule, APP_ROUTES_MODULE_PROVIDER} from "./app.routes";
[...]
@NgModule({
imports: [
BrowserModule,
HttpModule,
FormsModule,
AppRoutesModule,
[...]
],
declarations: [
AppComponent
],
providers: [
[...]
APP_ROUTES_MODULE_PROVIDER
],
bootstrap: [
AppComponent
]
})
export class AppModule {
}
Das Fenster Network
in den Dev-Tools zeigt nun sehr deutlich, dass die Anwendung wie gewünscht erst nach dem Programmstart mit ungenützten Ressourcen und Preloading das Module lädt:

Selektives Preloading mit eigener Preloading-Strategie
Victor Savkin hat auf der AngularConnect 2016 in London auch gezeigt, wie sich eine Angular-Anwendung beim Preloading auf bestimmte Module beschränken kann. Dazu erhalten die gewünschten Routen eine benutzerdefinierte Eigenschaft preload
:
// app.routes.ts
import {Routes, RouterModule} from @angular/router;
import {HomeComponent} from "./modules/home/home/home.component";
const ROUTE_CONFIG: Routes = [
{
path: home,
component: HomeComponent
},
{
path: flug-buchen,
loadChildren: ./modules/flug/flug.module#FlugModule,
data: { preload: true }
},
{
path: **,
redirectTo: home
}
];
export const AppRoutesModule = RouterModule.forRoot(ROUTE_CONFIG, { preloadingStrategy: CustomPreloadingStrategy });
export const APP_ROUTES_MODULE_PROVIDER = [CustomPreloadingStrategy];
Die Eigenschaft data
ist für solche benutzerdefinierten Erweiterungen vorgesehen. Die Preloading-Strategie kann nun prüfen, ob die übergebene Route diese Eigenschaft aufweist sowie ob sie truthy ist:
// custom-preloading-strategy.ts
import {PreloadingStrategy, Route} from "@angular/router";
import {Observable} from rxjs;
export class CustomPreloadingStrategy implements PreloadingStrategy {
preload(route: Route, fn: () => Observable<any>): Observable<any> {
if (route.data[preload]) {
return fn();
}
else {
return Observable.of(null);
}
}
}
In diesem Fall lädt sie die Route mit der entgegengenommenen Funktion und retourniert das von ihr erhaltene Observable. Ansonsten liefert sie ein (Dummy-)Observable, welches den Wert null
transportiert, zurück.
Das Registrieren der CustomPreloadingStrategy
erfolgt darauf hin wie weiter oben beschrieben.