Angular Preloading Strategies
Introduction
When building large Angular applications, we often split our code into feature modules that are loaded on demand (lazy loading). This improves the initial load time of our application, as users only download the code they need for the current view. However, this approach can lead to delays when navigating to these lazy-loaded modules for the first time.
Angular's preloading strategies provide a solution to this challenge by allowing you to define how and when these lazy-loaded modules should be downloaded. In this tutorial, we'll explore different preloading strategies and learn how to implement them in your Angular applications.
Understanding Preloading
Before diving into specific strategies, let's understand what preloading is in Angular:
- Lazy Loading: Modules are loaded only when needed (when the user navigates to a route)
- Preloading: Modules are still lazy-loaded but downloaded in the background after the main application loads
- Eager Loading: All modules are loaded when the application starts (not recommended for large apps)
Preloading gives us the best of both worlds - fast initial load times and smoother subsequent navigation.
Built-in Preloading Strategies
Angular provides two built-in preloading strategies:
1. No Preloading Strategy
This is the default strategy. Modules are loaded only when they are needed (pure lazy loading).
import { RouterModule, NoPreloading } from '@angular/router';
@NgModule({
imports: [
RouterModule.forRoot(routes, {
preloadingStrategy: NoPreloading
})
],
exports: [RouterModule]
})
export class AppRoutingModule { }
2. PreloadAllModules Strategy
This strategy preloads all lazy-loaded modules after the main application has loaded.
import { RouterModule, PreloadAllModules } from '@angular/router';
@NgModule({
imports: [
RouterModule.forRoot(routes, {
preloadingStrategy: PreloadAllModules
})
],
exports: [RouterModule]
})
export class AppRoutingModule { }
Implementing Custom Preloading Strategies
The built-in strategies may not always meet your needs. Fortunately, Angular allows you to create custom preloading strategies.
Creating a Custom Preloading Strategy
Here's an example of a custom preloading strategy that only preloads routes with a specific preload
data property set to true
:
import { Injectable } from '@angular/core';
import { PreloadingStrategy, Route } from '@angular/router';
import { Observable, of } from 'rxjs';
@Injectable({
providedIn: 'root'
})
export class SelectivePreloadingStrategy implements PreloadingStrategy {
preload(route: Route, load: () => Observable<any>): Observable<any> {
// Only preload routes that have data.preload set to true
return route.data && route.data.preload ? load() : of(null);
}
}
Using the Custom Strategy
To use this custom strategy, first define it in your routing module:
import { RouterModule } from '@angular/router';
import { SelectivePreloadingStrategy } from './selective-preloading-strategy';
const routes = [
{
path: 'admin',
loadChildren: () => import('./admin/admin.module').then(m => m.AdminModule),
data: { preload: true } // This module will be preloaded
},
{
path: 'reports',
loadChildren: () => import('./reports/reports.module').then(m => m.ReportsModule),
data: { preload: false } // This module will NOT be preloaded
}
];
@NgModule({
imports: [
RouterModule.forRoot(routes, {
preloadingStrategy: SelectivePreloadingStrategy
})
],
exports: [RouterModule],
providers: [SelectivePreloadingStrategy]
})
export class AppRoutingModule { }
Advanced Custom Preloading Strategy Examples
Let's explore some more advanced custom preloading strategies:
Network-Aware Preloading
This strategy preloads modules only when the user has a fast connection:
import { Injectable } from '@angular/core';
import { PreloadingStrategy, Route } from '@angular/router';
import { Observable, of } from 'rxjs';
@Injectable({
providedIn: 'root'
})
export class NetworkAwarePreloadingStrategy implements PreloadingStrategy {
preload(route: Route, load: () => Observable<any>): Observable<any> {
// Check connection speed using the Network Information API
const connection = (navigator as any).connection;
if (connection && (connection.saveData ||
connection.effectiveType.includes('2g'))) {
// Don't preload if using save-data or on a slow connection
return of(null);
}
// Otherwise, preload
return load();
}
}
Preload After Delay
This strategy waits for a specified time after the application loads before starting to preload modules:
import { Injectable } from '@angular/core';
import { PreloadingStrategy, Route } from '@angular/router';
import { Observable, of, timer } from 'rxjs';
import { switchMap } from 'rxjs/operators';
@Injectable({
providedIn: 'root'
})
export class DelayedPreloadingStrategy implements PreloadingStrategy {
preload(route: Route, load: () => Observable<any>): Observable<any> {
const loadAfterDelay = timer(5000).pipe(
switchMap(_ => load())
);
return route.data && route.data.preload ? loadAfterDelay : of(null);
}
}
Real-World Implementation Example
Let's put everything together in a real-world example for an e-commerce application:
// app-routing.module.ts
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { CustomPreloadingStrategy } from './custom-preloading-strategy';
const routes: Routes = [
{
path: '',
loadChildren: () => import('./home/home.module').then(m => m.HomeModule)
},
{
path: 'products',
loadChildren: () => import('./products/products.module').then(m => m.ProductsModule),
data: { preload: true } // High priority - preload this
},
{
path: 'cart',
loadChildren: () => import('./cart/cart.module').then(m => m.CartModule),
data: { preload: true } // High priority - preload this
},
{
path: 'account',
loadChildren: () => import('./account/account.module').then(m => m.AccountModule),
data: { preload: false } // Low priority - don't preload
},
{
path: 'support',
loadChildren: () => import('./support/support.module').then(m => m.SupportModule),
data: { preload: false } // Low priority - don't preload
}
];
@NgModule({
imports: [RouterModule.forRoot(routes, {
preloadingStrategy: CustomPreloadingStrategy
})],
exports: [RouterModule],
providers: [CustomPreloadingStrategy]
})
export class AppRoutingModule { }
// custom-preloading-strategy.ts
import { Injectable } from '@angular/core';
import { PreloadingStrategy, Route } from '@angular/router';
import { Observable, of } from 'rxjs';
@Injectable({
providedIn: 'root'
})
export class CustomPreloadingStrategy implements PreloadingStrategy {
preload(route: Route, load: () => Observable<any>): Observable<any> {
// We check if:
// 1. The route has data.preload flag
// 2. We're not on a mobile device (simplified check)
// 3. We're not on a slow connection
const isMobile = window.innerWidth < 768;
const connection = (navigator as any).connection;
const isSlowConnection = connection &&
(connection.saveData || connection.effectiveType.includes('2g'));
if (route.data && route.data.preload && !isMobile && !isSlowConnection) {
console.log(`Preloading: ${route.path}`);
return load();
}
return of(null);
}
}
In this example, we:
- Set up a prioritization system with the
preload
data property - Skip preloading on mobile devices and slow connections
- Preload important modules (products, cart) that users are likely to visit
- Defer less critical modules (account, support) to be loaded on demand
Monitoring Preloading in Action
You can observe preloading in action by looking at the Network tab in your browser's developer tools. When you load your application:
- First, the main bundle loads
- Shortly after, you'll see additional JavaScript chunks being downloaded in the background
- When you navigate to a preloaded route, it loads instantly as the code is already available
Summary
Angular preloading strategies offer a powerful way to balance between initial load performance and a smooth user experience. In this tutorial, we've covered:
- The concept of preloading in Angular
- Built-in strategies: NoPreloading and PreloadAllModules
- Creating custom preloading strategies for more granular control
- Implementing network-aware and delayed preloading strategies
- A real-world example for an e-commerce application
By implementing the right preloading strategy, you can significantly improve the perceived performance of your Angular application.
Additional Resources
Exercises
- Implement a custom preloading strategy that preloads modules based on user roles (e.g., admin modules for admin users)
- Create a strategy that tracks which routes users navigate to most often and prioritizes preloading those
- Add analytics to your preloading strategy to measure its effectiveness
- Implement a strategy that only preloads during idle periods using
requestIdleCallback
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)