Skip to main content

Lazy Loading

Lazy Load Components (Standalone)

import { Routes } from '@angular/router';

export const routes: Routes = [
{
path: '',
loadComponent: () => import('./home/home.component')
.then(m => m.HomeComponent)
},
{
path: 'about',
loadComponent: () => import('./about/about.component')
.then(m => m.AboutComponent)
},
{
path: 'users/:id',
loadComponent: () => import('./user-detail/user-detail.component')
.then(m => m.UserDetailComponent)
}
];

Lazy Load Routes

// Main routes
export const routes: Routes = [
{ path: '', component: HomeComponent },
{
path: 'admin',
loadChildren: () => import('./admin/admin.routes')
.then(m => m.ADMIN_ROUTES)
},
{
path: 'shop',
loadChildren: () => import('./shop/shop.routes')
.then(m => m.SHOP_ROUTES)
}
];

// admin/admin.routes.ts
import { Routes } from '@angular/router';

export const ADMIN_ROUTES: Routes = [
{
path: '',
loadComponent: () => import('./admin-dashboard.component')
.then(m => m.AdminDashboardComponent)
},
{
path: 'users',
loadComponent: () => import('./admin-users.component')
.then(m => m.AdminUsersComponent)
}
];

Preloading Strategies

No Preloading (Default)

import { provideRouter } from '@angular/router';

bootstrapApplication(AppComponent, {
providers: [
provideRouter(routes) // No preloading
]
});

PreloadAllModules

import { provideRouter, withPreloading, PreloadAllModules } from '@angular/router';

bootstrapApplication(AppComponent, {
providers: [
provideRouter(
routes,
withPreloading(PreloadAllModules) // Preload all lazy routes
)
]
});

Custom Preloading Strategy

import { Injectable } from '@angular/core';
import { PreloadingStrategy, Route } from '@angular/router';
import { Observable, of, timer } from 'rxjs';
import { mergeMap } from 'rxjs/operators';

@Injectable({ providedIn: 'root' })
export class CustomPreloadStrategy implements PreloadingStrategy {
preload(route: Route, load: () => Observable<any>): Observable<any> {
// Preload if route has data.preload = true
if (route.data?.['preload']) {
console.log('Preloading:', route.path);
return load();
}
return of(null);
}
}

// Usage
export const routes: Routes = [
{
path: 'admin',
loadChildren: () => import('./admin/admin.routes'),
data: { preload: true } // Will preload
},
{
path: 'shop',
loadChildren: () => import('./shop/shop.routes')
// Won't preload
}
];

bootstrapApplication(AppComponent, {
providers: [
provideRouter(
routes,
withPreloading(CustomPreloadStrategy)
)
]
});

Lazy Load with Guards

export const routes: Routes = [
{
path: 'admin',
canActivate: [authGuard],
loadChildren: () => import('./admin/admin.routes')
.then(m => m.ADMIN_ROUTES)
}
];

Bundle Analysis

# Build and analyze bundles
ng build --stats-json
npx webpack-bundle-analyzer dist/your-app/stats.json

Named Chunks

// Webpack will create named chunks
{
path: 'admin',
loadChildren: () => import(
/* webpackChunkName: "admin" */
'./admin/admin.routes'
).then(m => m.ADMIN_ROUTES)
}

Lazy Load Services

// Service only loaded with lazy route
@Injectable()
export class AdminService {
// ...
}

// Provide in lazy route
export const ADMIN_ROUTES: Routes = [
{
path: '',
component: AdminComponent,
providers: [AdminService] // Only loaded with this route
}
];

Testing Lazy Loading

describe('Lazy Loading', () => {
it('should lazy load admin module', async () => {
const router = TestBed.inject(Router);
const fixture = TestBed.createComponent(AppComponent);

await router.navigate(['/admin']);
fixture.detectChanges();

const compiled = fixture.nativeElement;
expect(compiled.querySelector('app-admin')).toBeTruthy();
});
});

Best Practices

  • Lazy load feature routes (not core functionality)
  • Use PreloadAllModules for better UX on fast connections
  • Create custom strategies for conditional preloading
  • Lazy load admin/dashboard routes
  • Keep core features eagerly loaded
  • Use bundle analysis to optimize
  • Preload based on user behavior or route priority
  • Group related components into route modules
  • Test lazy routes to ensure they work

Performance Tips

// ✅ Good: Separate features
{
path: 'admin',
loadChildren: () => import('./admin/admin.routes')
}

// ❌ Bad: Too granular
{
path: 'user',
loadComponent: () => import('./user.component') // Too small
}

// ✅ Good: Group related components
{
path: 'dashboard',
loadChildren: () => import('./dashboard/dashboard.routes')
// dashboard.routes contains multiple related components
}

Monitor Load Performance

@Injectable({ providedIn: 'root' })
export class LoadTimePreloadStrategy implements PreloadingStrategy {
preload(route: Route, load: () => Observable<any>): Observable<any> {
if (route.data?.['preload']) {
const start = performance.now();

return load().pipe(
tap(() => {
const end = performance.now();
console.log(`Loaded ${route.path} in ${end - start}ms`);
})
);
}
return of(null);
}
}