Routing & Navigation
Router configuration, lazy loading, guards, resolvers, child routes
Angular Router maps URL paths to components and manages navigation state. It supports lazy loading (reducing initial bundle size), route guards (protecting routes), resolvers (pre-loading data), and child routes (nested layouts). Understanding the router's configuration and lifecycle hooks is essential for any multi-page Angular app.
Key Points
- Routes array: { path, component } or { path, loadComponent } for lazy loading standalone components
- Route params: /users/:id → inject ActivatedRoute → route.params or inject(ActivatedRouteSnapshot)
- Lazy loading: loadComponent: () => import('./feature.component').then(m => m.FeatureComponent) — separate JS chunk
- canActivate guard (functional): inject services, return true/false/UrlTree — functional guards with inject() since Angular 15
- canDeactivate guard: prevent navigation away from unsaved forms — prompt user to confirm
- Resolver: loads data before route activates — resolve: { user: userResolver } — data available in route.data
- Child routes / nested outlets: { path: 'admin', children: [...], component: AdminLayoutComponent }
- Router events: NavigationStart, NavigationEnd — use for loading spinners, analytics
- withComponentInputBinding(): router params automatically bound to component @Input — Angular 16+
Angular Router: lazy standalone components, functional guards and resolvers, child routes, withComponentInputBinding
// app.routes.ts — Angular 17 functional style
export const routes: Routes = [
{ path: '', redirectTo: '/home', pathMatch: 'full' },
{
path: 'home',
loadComponent: () => import('./home/home.component').then(m => m.HomeComponent)
},
{
path: 'admin',
loadComponent: () => import('./admin/admin-layout.component').then(m => m.AdminLayoutComponent),
canActivate: [authGuard], // functional guard
children: [
{ path: '', loadComponent: () => import('./admin/dashboard.component').then(m => m.DashboardComponent) },
{ path: 'users/:id', loadComponent: () => import('./admin/user-detail.component').then(m => m.UserDetailComponent),
resolve: { user: userResolver } }
]
},
{ path: '**', loadComponent: () => import('./not-found.component').then(m => m.NotFoundComponent) }
];
// Functional guard
export const authGuard: CanActivateFn = (route, state) => {
const auth = inject(AuthService);
const router = inject(Router);
return auth.isLoggedIn()
? true
: router.createUrlTree(['/login'], { queryParams: { returnUrl: state.url } });
};
// Functional resolver
export const userResolver: ResolveFn<User> = (route) => {
return inject(UserService).getUser(route.paramMap.get('id')!);
};
// Component — params via @Input (Angular 16+ withComponentInputBinding)
@Component({ standalone: true, ... })
export class UserDetailComponent {
@Input() id!: string; // from route param :id
@Input() user!: User; // from resolver
ngOnChanges() { /* re-load on param change */ }
}
// app.config.ts
export const appConfig: ApplicationConfig = {
providers: [
provideRouter(routes,
withComponentInputBinding(), // @Input from route params
withViewTransitions(), // CSS View Transitions API
withDebugTracing() // verbose router logging (dev only)
)
]
};Real-World Example
Lazy loading is the most impactful Angular performance optimisation — splitting a 2MB bundle into 200KB initial + 10 feature chunks means users load only what they need. The rule of thumb: every feature route should be lazy. Use --stats-json and webpack-bundle-analyzer to see which modules are in the initial bundle and find accidental eager imports.