Lazy loading splits route or template code from the initial bundle. Choose boundaries that reduce initial work without producing a maze of tiny chunks, then test loading, error, preloading, and layout-stability behavior.
Lazy loading moves feature code out of the initial bundle and loads it when a route or deferred template needs it. The benefit depends on bundle size, navigation patterns, caching, and network conditions, so confirm it in build and runtime measurements.
Use loadComponent for a standalone route and loadChildren for a lazy route collection. Keep route-level providers beside the lazy route when state should be created on entry and released with that route environment.
Each boundary adds a request and a loading state, so split by meaningful features rather than every small component. Verify build output because a shared eager import can pull a dependency back into the initial bundle.
Use loadComponent for one standalone route and loadChildren for a route collection. The dynamic import expression creates a bundler boundary only when the imported feature is not also pulled into an eager dependency graph.
import { Routes } from '@angular/router';
export const routes: Routes = [
// Eagerly loaded - always in the main bundle
{ path: '', loadComponent: () => import('./home/home.component').then(m => m.HomeComponent) },
// Lazy loaded single component - separate chunk
{
path: 'about',
loadComponent: () => import('./about/about.component').then(m => m.AboutComponent)
},
// Lazy loaded feature module (group of routes) - separate chunk
{
path: 'admin',
loadChildren: () => import('./admin/admin.routes').then(m => m.adminRoutes)
},
// Lazy loaded with route guard
{
path: 'dashboard',
loadComponent: () => import('./dashboard/dashboard.component').then(m => m.DashboardComponent),
canActivate: [authGuard]
}
];
// admin/admin.routes.ts - loaded only when user visits /admin
import { Routes } from '@angular/router';
export const adminRoutes: Routes = [
{
path: '',
loadComponent: () => import('./admin-home/admin-home.component').then(m => m.AdminHomeComponent)
},
{
path: 'users',
loadComponent: () => import('./users/users.component').then(m => m.UsersComponent)
},
{
path: 'settings',
loadComponent: () => import('./settings/settings.component').then(m => m.SettingsComponent)
}
];
A @defer block creates a template-level loading boundary with configurable triggers, prefetch behavior, placeholder, loading, and error states. Use it for deferrable view dependencies; use lazy routes when navigation owns the boundary.
import { Component } from '@angular/core';
import { HeavyChartComponent } from './heavy-chart.component';
import { CommentSectionComponent } from './comment-section.component';
@Component({
selector: 'app-page',
standalone: true,
template: `
<h1>Article Title</h1>
<p>Article content...</p>
<!-- Load chart only when it enters the viewport -->
@defer (on viewport) {
<app-heavy-chart />
} @loading {
<p>Loading chart...</p>
} @placeholder {
<div class="chart-placeholder">Chart will appear here</div>
} @error {
<p>Failed to load chart.</p>
}
<!-- Load comments only on user interaction -->
@defer (on interaction) {
<app-comment-section />
} @placeholder {
<button>Load Comments</button>
}
<!-- Load after 2 seconds idle time -->
@defer (on idle; prefetch on immediate) {
<app-recommendations />
}
`
})
export class PageComponent {}
A preloading strategy may fetch lazy route code after initial navigation so later routes open faster. PreloadAllModules favors future navigation speed; NoPreloading preserves bandwidth until a route is requested.
A custom PreloadingStrategy can inspect route data and likely intent, but it should remain predictable and measurable. Preloading downloads code; it should not silently perform feature data requests or start expensive work.
Measure initial load, total transferred bytes, and target-route latency before adopting a broad strategy, especially for mobile or metered users.
| Strategy | Behaviour | Best for |
|---|---|---|
| NoPreloading (default) | Load only on navigation | Bandwidth-sensitive apps |
| PreloadAllModules | Preload all lazy modules after initial load | Small to medium apps |
| Custom strategy | Preload only routes with a specific flag | Large apps with selective preloading |
import { ApplicationConfig } from '@angular/core';
import { provideRouter, withPreloading, PreloadAllModules } from '@angular/router';
import { routes } from './app.routes';
export const appConfig: ApplicationConfig = {
providers: [
provideRouter(
routes,
withPreloading(PreloadAllModules) // preload all lazy modules in background
)
]
};
Place feature providers on the lazy route when their state should be created for that route environment and released when it is destroyed. Use canMatch when a route definition should be skipped before its lazy code is selected, such as choosing separate admin and user implementations for the same path.
A guard controls navigation experience, not server authorization. The backend must still verify every protected request. Avoid importing the lazy component into a root guard or eager service because that can pull the code back into the initial bundle.
export const routes: Routes = [
{
path: 'reports',
canMatch: [reportsAccessGuard],
providers: [ReportsFilterStore],
loadChildren: () =>
import('./reports/reports.routes').then(m => m.REPORT_ROUTES)
}
];
Verify a lazy boundary in both the production build output and the browser Network panel. The initial navigation should not download the feature chunk, the target navigation should request it once, and a later visit should use the browser cache according to the deployment policy.
Server rendering can render a lazy route on the server, so the route still needs browser hydration and asset testing. Keep browser-only APIs behind platform-safe boundaries and verify direct navigation to every lazy URL through the production web server fallback.
ng build
# Open the built application through its production-style server.
# Confirm the reports chunk is absent initially and requested on /reports.
A static import elsewhere can pull the component into the eager dependency graph even when a route also references it through loadComponent. Search barrel files, root component imports, shared configuration, and direct references in eager templates.
Lazy loading still reduces what blocks the initial route, but PreloadAllModules begins fetching every lazy route after startup.
Reserve the space in the placeholder.
Explore 500+ free tutorials across 20+ languages and frameworks.