The new Angular router supports lazy loading of modules. This approach can improve the initial load time of an Angular-based single-page application. Preloading, which Angular core team member Victor Savkin presented at AngularConnect 2016 in London, goes a step further by enabling another performance optimization: it utilizes unused resources after the application has started to fetch modules that might be requested via lazy loading later on. If the router actually needs those modules at a later point, they are available immediately.
In this post, I demonstrate how to use preloading in an Angular application. The complete sample code can be found here. It relies on Angular 2.1.0-beta.0 and Router 3.1.0-beta.0, which are the first releases that include this functionality.
Starting Point
The example presented here uses an AppModule that lazy loads a FlugModule. For this purpose, it refers to the module by its name and the file it resides in, using loadChildren:
// 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);
The final line in this listing creates a configured variant of the RouterModule based on the routing configuration and exports it through the variable AppRoutesModule. The AppModule, which acts as the root module here, references it:
// 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 {
}
To make loadChildren work with the shown straightforward syntax in the routing configuration together with Webpack 2, the angular2-router-loader is used. It can be installed via npm (npm i angular2-router-loader --save-dev) and serves as an additional loader for .ts files in the webpack.config.js:
[...]
module: {
loaders: [
[...],
{ test: /\.html$/, loaders: ['html-loader'] },
{ test: /\.ts$/, loaders: ['angular2-router-loader?loader=system', 'awesome-typescript-loader'], exclude: /node_modules/}
]
},
[...]
The parameter ?loader=system instructs the loader to fetch the modules requested via lazy loading using System.import.
Preloading
To enable preloading, starting with version 3.1.0 of the router, you only need to specify a PreloadingStrategy when creating the configured AppRoutesModule:
import {Routes, RouterModule, PreloadAllModules} from '@angular/router';
[...]
export const AppRoutesModule = RouterModule.forRoot(ROUTE_CONFIG, { preloadingStrategy: PreloadAllModules });
The strategy used here, PreloadAllModules, causes the Angular application to fetch all modules via preloading after the application has started.
The outcome of this endeavor can be observed in Chrome's F12 DevTools under the Network tab. Since loading local files happens very quickly, it is advisable to throttle the network speed for this examination. The following figure illustrates the loading behavior on a simulated 3G connection, for example:
When the page loads, the observed window shows that Angular fetches the bundle 0.js containing the FlugModule only after the application has started. However, since this bundle is quite small, one has to look very closely to notice this. Therefore, the next section describes an experiment that makes this behavior easier to understand.
Observing Preloading with an Experiment
To better demonstrate that preloading only begins after the application has started, a custom preloading strategy is used in this section. This strategy introduces a delay of several seconds with RxJS before it takes care of loading the module.
To provide a custom preloading strategy, the PreloadingStrategy interface must be implemented:
// 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());
}
}
Angular passes the route to be loaded to the preload method of the PreloadingStrategy, along with a function that performs the actual loading. This allows the strategy to decide whether the route in question should be fetched via preloading and, if so, to initiate that process. The returned Observable notifies Angular when preload has completed its work.
The implementation under consideration creates an observable that emits the (dummy) value true and dispatches it with a delay of 7 seconds. After this time span, flatMap performs the preloading.
To use the CustomPreloadingStrategy, it must be referenced when creating the configured AppRoutesModule. Since the strategy is only used as a token at this point, Angular additionally requires a provider for it:
// app.routes.ts
[...]
export const AppRoutesModule = RouterModule.forRoot(ROUTE_CONFIG, { preloadingStrategy: CustomPreloadingStrategy });
export const APP_ROUTES_MODULE_PROVIDER = [CustomPreloadingStrategy];
So that the provider is available to the application, the AppModule references it through its providers array. The configured AppRoutesModule naturally still references it as well:
// 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 {
}
The Network tab in the DevTools now clearly shows that the application, as intended, only loads the module after startup using idle resources and preloading:
Selective Preloading with a Custom Strategy
Victor Savkin also demonstrated at AngularConnect 2016 in London how an Angular application can restrict preloading to particular modules. For this, the desired routes are given a custom property 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];
The data property is intended for such custom extensions. The preloading strategy can now check whether the provided route has this property and whether it is truthy:
// 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 that case, it loads the route with the function it received and returns the observable obtained from it. Otherwise, it returns a (dummy) observable that carries the value null.
The registration of the CustomPreloadingStrategy then follows the approach described earlier.


