Skill v1.0.1
currentLLM-judged scan95/1003 files
version: "1.0.1" 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. metadata: category: development source: repository: 'https://github.com/analogjs/angular-skills' path: skills/angular-routing license_path: LICENSE commit: 610c90eb9490194bcff703f343f97fa0e00bdb2f
Angular Routing
Configure routing in Angular v20+ with lazy loading, functional guards, and signal-based route parameters.
Basic Setup
// app.routes.tsimport { Routes } from '@angular/router';export const routes: Routes = [{ path: '', redirectTo: '/home', pathMatch: 'full' },{ path: 'home', component: Home },{ path: 'about', component: About },{ path: '**', component: NotFound },];// app.config.tsimport { ApplicationConfig } from '@angular/core';import { provideRouter } from '@angular/router';import { routes } from './app.routes';export const appConfig: ApplicationConfig = {providers: [provideRouter(routes),],};// app.component.tsimport { Component } from '@angular/core';import { RouterOutlet, RouterLink, RouterLinkActive } from '@angular/router';@Component({selector: 'app-root',imports: [RouterOutlet, RouterLink, RouterLinkActive],template: `<nav><a routerLink="/home" routerLinkActive="active">Home</a><a routerLink="/about" routerLinkActive="active">About</a></nav><router-outlet />`,})export class App {}
Lazy Loading
Load feature modules on demand:
// app.routes.tsexport const routes: Routes = [{ path: '', redirectTo: '/home', pathMatch: 'full' },{ path: 'home', component: Home },// 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.Settings),},];// admin/admin.routes.tsexport const adminRoutes: Routes = [{ path: '', component: AdminDashboard },{ path: 'users', component: AdminUsers },{ path: 'settings', component: AdminSettings },];
Route Parameters
With Signal Inputs (Recommended)
// Route config{ path: 'users/:id', component: UserDetail }// Component - use input() for route paramsimport { Component, input, computed } from '@angular/core';@Component({selector: 'app-user-detail',template: `<h1>User {{ id() }}</h1>`,})export class UserDetail {// Route param as signal inputid = input.required<string>();// Computed based on route paramuserId = computed(() => parseInt(this.id(), 10));}
Enable with withComponentInputBinding():
// app.config.tsimport { provideRouter, withComponentInputBinding } from '@angular/router';export const appConfig: ApplicationConfig = {providers: [provideRouter(routes, withComponentInputBinding()),],};
Query Parameters
// Route: /search?q=angular&page=1@Component({...})export class Search {// Query params as inputsq = input<string>('');page = input<string>('1');currentPage = computed(() => parseInt(this.page(), 10));}
With ActivatedRoute (Alternative)
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 UserDetail {private route = inject(ActivatedRoute);// Convert route params to signalid = toSignal(this.route.paramMap.pipe(map(params => params.get('id'))),{ initialValue: null });// Query paramsquery = toSignal(this.route.queryParamMap.pipe(map(params => params.get('q'))),{ initialValue: '' });}
Functional Guards
Auth Guard
// guards/auth.guard.tsimport { inject } from '@angular/core';import { CanActivateFn, Router } from '@angular/router';export const authGuard: CanActivateFn = (route, state) => {const authService = inject(Auth);const router = inject(Router);if (authService.isAuthenticated()) {return true;}// Redirect to login with return URLreturn router.createUrlTree(['/login'], {queryParams: { returnUrl: state.url },});};// Usage in routes{path: 'dashboard',component: Dashboard,canActivate: [authGuard],}
Role Guard
export const roleGuard = (allowedRoles: string[]): CanActivateFn => {return (route, state) => {const authService = inject(Auth);const router = inject(Router);const userRole = authService.currentUser()?.role;if (userRole && allowedRoles.includes(userRole)) {return true;}return router.createUrlTree(['/unauthorized']);};};// Usage{path: 'admin',component: Admin,canActivate: [authGuard, roleGuard(['admin', 'superadmin'])],}
Can Deactivate Guard
export interface CanDeactivate {canDeactivate: () => boolean | Promise<boolean>;}export const unsavedChangesGuard: CanDeactivateFn<CanDeactivate> = (component) => {if (component.canDeactivate()) {return true;}return confirm('You have unsaved changes. Leave anyway?');};// Component implementation@Component({...})export class Edit implements CanDeactivate {form = inject(FormBuilder).group({...});canDeactivate(): boolean {return !this.form.dirty;}}// Route{path: 'edit/:id',component: Edit,canDeactivate: [unsavedChangesGuard],}
Resolvers
Pre-fetch data before route activation:
// resolvers/user.resolver.tsimport { inject } from '@angular/core';import { ResolveFn } from '@angular/router';export const userResolver: ResolveFn<User> = (route) => {const userService = inject(User);const id = route.paramMap.get('id')!;return userService.getById(id);};// Route config{path: 'users/:id',component: UserDetail,resolve: { user: userResolver },}// Component - access resolved data via input@Component({...})export class UserDetail {user = input.required<User>();}
Nested Routes
// Parent route with childrenexport const routes: Routes = [{path: 'products',component: ProductsLayout,children: [{ path: '', component: ProductList },{ path: ':id', component: ProductDetail },{ path: ':id/edit', component: ProductEdit },],},];// ProductsLayout@Component({imports: [RouterOutlet],template: `<h1>Products</h1><router-outlet /> <!-- Child routes render here -->`,})export class ProductsLayout {}
Programmatic Navigation
import { Component, inject } from '@angular/core';import { Router } from '@angular/router';@Component({...})export class Product {private router = inject(Router);// Navigate to routegoToProducts() {this.router.navigate(['/products']);}// Navigate with paramsgoToProduct(id: string) {this.router.navigate(['/products', id]);}// Navigate with query paramssearch(query: string) {this.router.navigate(['/search'], {queryParams: { q: query, page: 1 },});}// Navigate relative to current routegoToEdit() {this.router.navigate(['edit'], { relativeTo: this.route });}// Replace current history entryreplaceUrl() {this.router.navigate(['/new-page'], { replaceUrl: true });}}
Route Data
// Static route data{path: 'admin',component: Admin,data: {title: 'Admin Dashboard',roles: ['admin'],},}// Access in component@Component({...})export class AdminCmpt {title = input<string>(); // From route dataroles = input<string[]>(); // From route data}// Or via ActivatedRouteprivate route = inject(ActivatedRoute);data = toSignal(this.route.data);
Router Events
import { Router, NavigationStart, NavigationEnd } from '@angular/router';import { filter } from 'rxjs';@Component({...})export class AppMain {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.