• 30. September 2016

Performanceoptimierung mit Preloading und dem neuen Angular Router

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:

Ladeverhalten nach Aktivieren von Preloading

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:

Ladeverhalten mit eigener Preloading-Strategie

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.

 

 
Hier können Sie eine Anfrage für eine unverbindliche Schulung ode Beratung bzw. einen Workshop erstellen.
 
Unverbindliche Anfrage
 
 

Schulungen

Angular Schulung: Strukturierte Einführung

Lernen Sie in dieser interaktiven Schulung anhand einer Beispielanwendung den Einsatz von Angular für Ihre erfolgreichen Projekte kennen. Sie durchdringen die Hintergründe und bauen von der ersten Minute an auf Best Practices auf.

Details

Advanced Angular: Enterprise-Anwendungen und Architektur

In dieser Schulung erfahren Sie alles für die Entwicklung großer Anwendungen mit Angular: Mono-Repos, Micro-Apps, State Management, Performance und mehr

Details

Angular: Strukturierte Einführung

Seit der Ankündigung von Angular (2+) fragen sich Entwicklungs-Teams, welche Migrationspfade für AngularJS-1.x-Anwendungen vorliegen werden. Das im Lieferumfang von Angular enthaltene ngUpgrade bietet eine Antwort darauf. Es erlaubt einen Parallelbetrieb von AngularJS 1.x und Angular (2+) und stellt somit die Grundlage für eine schrittweise Migration dar.

Details

Progressive Web-Apps mit Angular

Progressive Web Apps bieten den Komfort nativer Anwendungen, indem sie auf moderne Browser APIs, wie Service Worker, setzen. Sie sind installierbar sowie offlinefähig und nutzen Hintergrundprozesse für Datensynchronisation und Push-Notifications. Diese Schulung zeigt anhand eines durchgehenden Beispiels was sich genau hinter diesem neuen Konzept verbirgt, wie solche Anwendungen mit Angular entwickelt werden und wie Sie in Ihren Projekten von den dahinterstehenden Ideen profitieren.

Details

Reaktive Architekturen mit Angular und Redux

Dieses interaktive Seminar vermittelt, wie Sie reaktive Anwendungen mit Angular entwickeln können.

Details

TypeScript

TypeScript gibt Ihnen alle Möglichkeiten der neuen JavaScript-Standards und zusätzlich ein statisches Typsystem, das dabei hilft, Fehler möglichst früh zu erkennen. Außerdem ist TypeScript die Grundlage für Angular. In diesem interaktiven Seminar lernen Sie diese mächtige Sprache anhand einer Fallstudie kennen.

Details

Weitere Schulungen ...