This article is for versions of Angular 2, Angular 4, Angular 5 and later.

This article has been updated to use the new RxJS Lettable Operators.

Angular has many features that allow us to configure apps to be as fast and high preforming as possible. One of the critical features that enable responsive fast Angular apps is the ability to lazy load code with the Angular Router. This allows our initial bundles to remain small ensuring faster downloads and start up times for our app.

The Angular router has the amazing ability to load code on demand as the user routes between views on our apps. By splitting our features into stand alone Angular Modules we can lazy load the module when the user clicks the link to navigate to this feature.

While this is powerful we can further optimize this. Built into Angular we can use its preload strategy. Using this preload strategy we can lazy load all modules in our app in the background. Keeping our main initial bundle small on first load and lazy loading all our modules in the background. This means that when the user clicks the link the module has been preloaded already into memory. This allows Angular to immediately start rendering instead of waiting for the module to download over the network.

The preload strategy build into Angular looks something like the following in our app routing module:



@NgModule({
  imports: [RouterModule.forRoot(routes, { preloadingStrategy: PreloadAllModules })], // Define Preloading Strategies
  exports: [RouterModule],
  providers: [AppCustomPreloader]
})
export class AppRoutingModule { }


This is great for performance and lazy loading progressively but we can do better.

Custom Preload Strategies

With the Angular Router we get the PreloadAllModules strategy for free. This works well but if our app is very large preloading every module in the background may cause unnecessary data to be loaded in the background. Ideally we would like to preload the core features or most common user paths in our app. This will allow core features to render immediately when the user navigates to the feature while keeping our core bundle small. As well as we continue to lazy load the rest of the less used features on demand when the user clicks the link.

Lets take a use case example app. In my app I have three features. The first feature is my landing page / core home feature. We will call this feature-1. My second feature is a often use feature in may app so I would like to preload just this feature. We will call this feature feature-2. My last feature is rarely used so I would like to load this only if the user navigate to it. We will call this feature feature-3.



// App Features
// feature-1: load with main core bundle
// feature-2: preload in background to be ready to use when user navigates to feature-2
// feature-3: only lazy load if the user navigates to feature-3


With out app set up we now want to define our own custom route preload strategy. Since we want to mark certain routes to be preloaded and others loaded on demand we need a way to define this information. In our example below we use the data property on our route configs to define when our route should be preloaded.



import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';

import { AppCustomPreloader } from './app-routing-loader';
import { Feature1Component } from './feature-1/feature-1.component';

const routes: Routes = [
  {
    path: '',
    redirectTo: 'feature-1',
    pathMatch: 'full'
  },
  {
    path: 'feature-1',
    component: Feature1Component
  },
  {
    path: 'feature-2',
    loadChildren: './feature-2/feature-2.module#Feature2Module',
    data: { preload: true }  // Custom property we will use to track what route to be preloaded
  },
  {
    path: 'feature-3',
    loadChildren: './feature-3/feature-3.module#Feature3Module'
  }
];

@NgModule({
  imports: [RouterModule.forRoot(routes, { preloadingStrategy: AppCustomPreloader })], // Using our own custom preloader
  exports: [RouterModule],
  providers: [AppCustomPreloader]
})
export class AppRoutingModule { }



On our routes we have an additional data property defined with a object containing a preload: true value. The data property is reserved by the router to allow developers to add their own custom values to their route configs. We will use this property to mark what routes should be preloaded based on our custom config.

In the example above we also have changed our preloading strategy to RouterModule.forRoot(routes, { preloadingStrategy: AppCustomPreloader }). Now instead of using Angular’s built in preload all strategy we are using our own. Let’s take a look at the AppCustomPreloader file.



import { PreloadingStrategy, Route } from '@angular/router';

import { Observable } from 'rxjs/Observable';
import { of } from 'rxjs/observable/of';

export class AppCustomPreloader implements PreloadingStrategy {
  preload(route: Route, load: Function): Observable<any> {
    return route.data && route.data.preload ? load() : of(null);
  }
}


Above is our custom preloader strategy. We implement the PreloadingStrategy interface provided by Angular. A preloading strategy expects a class with a method preload(). The preload method should return a Observable calling the load parameter or returning a Observable of null. In the preload method we can determine the logic of if we should preload the module or not. The preload method receives the active route as a parameter. With this route we can look at the data property we set and determine if the preload flag was set and load the module accordingly.

Running Example

Now that we have the custom preload strategy implemented let’s take a look at the running example. Our Angular CLI app has implemented the three features that we defined above. Using the Chrome Developer Tools we can inspect the Network traffic and see how our code is loaded.

Angular Lazy Loading with custom loading strategy

If we look at the network tools above we can see our main application code bundled and loaded by the browser. Towards the end of the network requests we see a 1.7...chunk.js file. This file is our custom eagerly loaded feature 2 that was loaded asynchronously after our application bootstrapped. To prove this is the case if we navigate to feature we will see no additional requests in the network as it was already preloaded.

Angular Lazy Loading with custom loading strategy

Now if we navigate to feature 3 we will see a new no demand network request for the feature 3 bundle as soon as we click the link to feature 3.

Angular Lazy Loading with custom loading strategy

With the Angular router and custom preload strategies we can customize how we load our code to best optimize our applications use cases. Check out the Angular CLI demo of this project below.

Demo