Skip to main content

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).

typescript
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.

typescript
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:

typescript
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:

typescript
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:

typescript
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:

typescript
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:

typescript
// 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 { }
typescript
// 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:

  1. Set up a prioritization system with the preload data property
  2. Skip preloading on mobile devices and slow connections
  3. Preload important modules (products, cart) that users are likely to visit
  4. 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:

  1. First, the main bundle loads
  2. Shortly after, you'll see additional JavaScript chunks being downloaded in the background
  3. 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

  1. Implement a custom preloading strategy that preloads modules based on user roles (e.g., admin modules for admin users)
  2. Create a strategy that tracks which routes users navigate to most often and prioritizes preloading those
  3. Add analytics to your preloading strategy to measure its effectiveness
  4. 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! :)