--- name: angular-routing description: Implement routing in Angular v20+ applications with lazy loading, functional guards, resolvers, and route parameters. Use for navigation setup, protected routes, route-based data loading, and nested routing. Triggers on route configuration, adding authentication guards, implementing lazy loading, or reading route parameters with signals. --- # Angular Routing Configure routing in Angular v20+ with lazy loading, functional guards, and signal-based route parameters. ## Basic Setup ```typescript // app.routes.ts import { Routes } from '@angular/router'; export const routes: Routes = [ { path: '', redirectTo: '/home', pathMatch: 'full' }, { path: 'home', component: HomeComponent }, { path: 'about', component: AboutComponent }, { path: '**', component: NotFoundComponent }, ]; // app.config.ts import { ApplicationConfig } from '@angular/core'; import { provideRouter } from '@angular/router'; import { routes } from './app.routes'; export const appConfig: ApplicationConfig = { providers: [ provideRouter(routes), ], }; // app.component.ts import { Component } from '@angular/core'; import { RouterOutlet, RouterLink, RouterLinkActive } from '@angular/router'; @Component({ selector: 'app-root', imports: [RouterOutlet, RouterLink, RouterLinkActive], template: ` `, }) export class AppComponent {} ``` ## Lazy Loading Load feature modules on demand: ```typescript // app.routes.ts export const routes: Routes = [ { path: '', redirectTo: '/home', pathMatch: 'full' }, { path: 'home', component: HomeComponent }, // Lazy load entire feature { path: 'admin', loadChildren: () => import('./admin/admin.routes').then(m => m.adminRoutes), }, // Lazy load single component { path: 'settings', loadComponent: () => import('./settings/settings.component').then(m => m.SettingsComponent), }, ]; // admin/admin.routes.ts export const adminRoutes: Routes = [ { path: '', component: AdminDashboardComponent }, { path: 'users', component: AdminUsersComponent }, { path: 'settings', component: AdminSettingsComponent }, ]; ``` ## Route Parameters ### With Signal Inputs (Recommended) ```typescript // Route config { path: 'users/:id', component: UserDetailComponent } // Component - use input() for route params import { Component, input, computed } from '@angular/core'; @Component({ selector: 'app-user-detail', template: `

User {{ id() }}

`, }) export class UserDetailComponent { // Route param as signal input id = input.required(); // Computed based on route param userId = computed(() => parseInt(this.id(), 10)); } ``` Enable with `withComponentInputBinding()`: ```typescript // app.config.ts import { provideRouter, withComponentInputBinding } from '@angular/router'; export const appConfig: ApplicationConfig = { providers: [ provideRouter(routes, withComponentInputBinding()), ], }; ``` ### Query Parameters ```typescript // Route: /search?q=angular&page=1 @Component({...}) export class SearchComponent { // Query params as inputs q = input(''); page = input('1'); currentPage = computed(() => parseInt(this.page(), 10)); } ``` ### With ActivatedRoute (Alternative) ```typescript import { Component, inject } from '@angular/core'; import { ActivatedRoute } from '@angular/router'; import { toSignal } from '@angular/core/rxjs-interop'; import { map } from 'rxjs'; @Component({...}) export class UserDetailComponent { private route = inject(ActivatedRoute); // Convert route params to signal id = toSignal( this.route.paramMap.pipe(map(params => params.get('id'))), { initialValue: null } ); // Query params query = toSignal( this.route.queryParamMap.pipe(map(params => params.get('q'))), { initialValue: '' } ); } ``` ## Functional Guards ### Auth Guard ```typescript // guards/auth.guard.ts import { inject } from '@angular/core'; import { CanActivateFn, Router } from '@angular/router'; export const authGuard: CanActivateFn = (route, state) => { const authService = inject(AuthService); const router = inject(Router); if (authService.isAuthenticated()) { return true; } // Redirect to login with return URL return router.createUrlTree(['/login'], { queryParams: { returnUrl: state.url }, }); }; // Usage in routes { path: 'dashboard', component: DashboardComponent, canActivate: [authGuard], } ``` ### Role Guard ```typescript export const roleGuard = (allowedRoles: string[]): CanActivateFn => { return (route, state) => { const authService = inject(AuthService); const router = inject(Router); const userRole = authService.currentUser()?.role; if (userRole && allowedRoles.includes(userRole)) { return true; } return router.createUrlTree(['/unauthorized']); }; }; // Usage { path: 'admin', component: AdminComponent, canActivate: [authGuard, roleGuard(['admin', 'superadmin'])], } ``` ### Can Deactivate Guard ```typescript export interface CanDeactivateComponent { canDeactivate: () => boolean | Promise; } export const unsavedChangesGuard: CanDeactivateFn = (component) => { if (component.canDeactivate()) { return true; } return confirm('You have unsaved changes. Leave anyway?'); }; // Component implementation @Component({...}) export class EditComponent implements CanDeactivateComponent { form = inject(FormBuilder).group({...}); canDeactivate(): boolean { return !this.form.dirty; } } // Route { path: 'edit/:id', component: EditComponent, canDeactivate: [unsavedChangesGuard], } ``` ## Resolvers Pre-fetch data before route activation: ```typescript // resolvers/user.resolver.ts import { inject } from '@angular/core'; import { ResolveFn } from '@angular/router'; export const userResolver: ResolveFn = (route) => { const userService = inject(UserService); const id = route.paramMap.get('id')!; return userService.getById(id); }; // Route config { path: 'users/:id', component: UserDetailComponent, resolve: { user: userResolver }, } // Component - access resolved data via input @Component({...}) export class UserDetailComponent { user = input.required(); } ``` ## Nested Routes ```typescript // Parent route with children export const routes: Routes = [ { path: 'products', component: ProductsLayoutComponent, children: [ { path: '', component: ProductListComponent }, { path: ':id', component: ProductDetailComponent }, { path: ':id/edit', component: ProductEditComponent }, ], }, ]; // ProductsLayoutComponent @Component({ imports: [RouterOutlet], template: `

Products

`, }) export class ProductsLayoutComponent {} ``` ## Programmatic Navigation ```typescript import { Component, inject } from '@angular/core'; import { Router } from '@angular/router'; @Component({...}) export class ProductComponent { private router = inject(Router); // Navigate to route goToProducts() { this.router.navigate(['/products']); } // Navigate with params goToProduct(id: string) { this.router.navigate(['/products', id]); } // Navigate with query params search(query: string) { this.router.navigate(['/search'], { queryParams: { q: query, page: 1 }, }); } // Navigate relative to current route goToEdit() { this.router.navigate(['edit'], { relativeTo: this.route }); } // Replace current history entry replaceUrl() { this.router.navigate(['/new-page'], { replaceUrl: true }); } } ``` ## Route Data ```typescript // Static route data { path: 'admin', component: AdminComponent, data: { title: 'Admin Dashboard', roles: ['admin'], }, } // Access in component @Component({...}) export class AdminComponent { title = input(); // From route data roles = input(); // From route data } // Or via ActivatedRoute private route = inject(ActivatedRoute); data = toSignal(this.route.data); ``` ## Router Events ```typescript import { Router, NavigationStart, NavigationEnd } from '@angular/router'; import { filter } from 'rxjs'; @Component({...}) export class AppComponent { private router = inject(Router); isNavigating = signal(false); constructor() { this.router.events.pipe( filter(e => e instanceof NavigationStart || e instanceof NavigationEnd) ).subscribe(event => { this.isNavigating.set(event instanceof NavigationStart); }); } } ``` For advanced patterns, see [references/routing-patterns.md](references/routing-patterns.md).