What is Lazy Loading?
Lazy loading is a technique where you load parts of your application on demand instead of loading everything upfront. This results in:
- Smaller initial bundle — faster first load
- Better performance — load code only when needed
- Improved user experience — faster time to interactive
How Lazy Loading Works
Without Lazy Loading:
┌──────────────────────────────┐
│ Initial Bundle (Everything) │ ← Large download
│ Home + Admin + Products + │
│ Reports + Settings + ... │
└──────────────────────────────┘
With Lazy Loading:
┌─────────────┐
│ Main Bundle │ ← Small initial download
│ (Core + Home)│
└─────────────┘
↓ User navigates to /admin
┌─────────────┐
│ Admin Chunk │ ← Loaded on demand
└─────────────┘
↓ User navigates to /reports
┌──────────────┐
│ Reports Chunk│ ← Loaded on demand
└──────────────┘Lazy Loading Routes
Using loadComponent (Standalone)
The simplest way to lazy-load a standalone component:
// app.routes.ts
export const routes: Routes = [
{ path: '', component: HomeComponent }, // Eagerly loaded
// Lazy loaded routes
{
path: 'products',
loadComponent: () =>
import('./products/products.component')
.then(m => m.ProductsComponent),
},
{
path: 'about',
loadComponent: () =>
import('./about/about.component')
.then(m => m.AboutComponent),
},
];Lazy Loading with Child Routes
// app.routes.ts
export const routes: Routes = [
{ path: '', component: HomeComponent },
{
path: 'admin',
loadChildren: () =>
import('./admin/admin.routes')
.then(m => m.adminRoutes),
},
];// admin/admin.routes.ts
import { Routes } from '@angular/router';
export const adminRoutes: Routes = [
{
path: '',
loadComponent: () =>
import('./admin-layout.component').then(m => m.AdminLayoutComponent),
children: [
{
path: '',
loadComponent: () =>
import('./dashboard/dashboard.component').then(m => m.DashboardComponent),
},
{
path: 'users',
loadComponent: () =>
import('./users/users.component').then(m => m.UsersComponent),
},
{
path: 'settings',
loadComponent: () =>
import('./settings/settings.component').then(m => m.SettingsComponent),
},
],
},
];A Complete Lazy-Loaded Application
// app.routes.ts
import { Routes } from '@angular/router';
import { HomeComponent } from './home/home.component';
import { authGuard } from './guards/auth.guard';
export const routes: Routes = [
// Eagerly loaded (always needed)
{ path: '', component: HomeComponent },
// Lazy loaded public pages
{
path: 'products',
loadComponent: () =>
import('./products/product-list.component')
.then(m => m.ProductListComponent),
},
{
path: 'products/:id',
loadComponent: () =>
import('./products/product-detail.component')
.then(m => m.ProductDetailComponent),
},
{
path: 'contact',
loadComponent: () =>
import('./contact/contact.component')
.then(m => m.ContactComponent),
},
// Lazy loaded authenticated area
{
path: 'dashboard',
canActivate: [authGuard],
loadChildren: () =>
import('./dashboard/dashboard.routes')
.then(m => m.dashboardRoutes),
},
// Lazy loaded admin area
{
path: 'admin',
canActivate: [authGuard],
loadChildren: () =>
import('./admin/admin.routes')
.then(m => m.adminRoutes),
},
// Login
{
path: 'login',
loadComponent: () =>
import('./auth/login.component')
.then(m => m.LoginComponent),
},
// 404
{
path: '**',
loadComponent: () =>
import('./not-found/not-found.component')
.then(m => m.NotFoundComponent),
},
];Preloading Strategies
By default, lazy-loaded chunks are loaded only when the route is navigated to. You can preload them in the background:
PreloadAllModules
Load all lazy routes in the background after the initial load:
// app.config.ts
import { provideRouter, withPreloading, PreloadAllModules } from '@angular/router';
export const appConfig: ApplicationConfig = {
providers: [
provideRouter(
routes,
withPreloading(PreloadAllModules),
),
],
};Custom Preloading Strategy
Only preload routes marked with data.preload:
import { PreloadingStrategy, Route } from '@angular/router';
import { Observable, of } from 'rxjs';
export class SelectivePreloadingStrategy implements PreloadingStrategy {
preload(route: Route, load: () => Observable<unknown>): Observable<unknown> {
if (route.data?.['preload']) {
return load(); // Preload this route
}
return of(null); // Don't preload
}
}// Route configuration
{
path: 'products',
loadComponent: () => import('./products/products.component')
.then(m => m.ProductsComponent),
data: { preload: true }, // This route will be preloaded
}// Register the strategy
provideRouter(
routes,
withPreloading(SelectivePreloadingStrategy),
),Lazy Loading Components (Non-Route)
You can also lazy-load components that aren't tied to routes using @defer:
@defer Block (Angular 17+)
<!-- Load component when it enters the viewport -->
@defer (on viewport) {
<app-heavy-chart [data]="chartData" />
} @placeholder {
<div class="chart-placeholder">Chart loading...</div>
} @loading (minimum 500ms) {
<app-spinner />
} @error {
<p>Failed to load chart component</p>
}Defer Triggers
| Trigger | Description |
|---|---|
on viewport | When element enters the viewport |
on idle | When browser is idle |
on interaction | When user interacts (click, focus) |
on hover | When user hovers over placeholder |
on immediate | Immediately after rendering |
on timer(5s) | After specified delay |
when condition | When expression becomes truthy |
<!-- Load on hover -->
@defer (on hover) {
<app-tooltip-content />
} @placeholder {
<span>Hover for details</span>
}
<!-- Load when a condition is met -->
@defer (when showComments) {
<app-comments [postId]="post.id" />
} @placeholder {
<button (click)="showComments = true">Load Comments</button>
}
<!-- Load after 3 seconds -->
@defer (on timer(3s)) {
<app-recommendations />
} @placeholder {
<div>Loading recommendations...</div>
}Verifying Lazy Loading
Check your build output to verify chunks are created:
ng build
# Output shows separate chunk files:
Initial chunk files | Names | Size
main.js | main | 150 kB
styles.css | styles | 10 kB
Lazy chunk files | Names | Size
chunk-ADMIN.js | admin | 45 kB
chunk-PRODUCTS.js | products | 30 kB
chunk-DASHBOARD.js | dashboard | 25 kBBest Practices
- Lazy load feature areas — admin, settings, reports should always be lazy
- Keep the main bundle small — only eagerly load what's needed for the first view
- Use
@deferfor heavy components within a page (charts, editors, maps) - Preload critical routes selectively — use
PreloadAllModulesor a custom strategy - Group related routes into feature route files for clean lazy loading
Next Steps
With routing and lazy loading covered, it's time to handle user input. Next, we'll explore template-driven forms — the simpler approach to building forms in Angular.