npx skills add https://github.com/analogjs/angular-skills --skill angular-routingHow Angular Routing fits into a Paperclip company.
Angular Routing drops into any Paperclip agent that handles this kind of work. Assign it to a specialist inside a pre-configured PaperclipOrg company and the skill becomes available on every heartbeat — no prompt engineering, no tool wiring.
Pre-configured AI company — 18 agents, 18 skills, one-time purchase.
SKILL.md395 linesExpandCollapse
---name: angular-routingdescription: 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.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: ```typescript// 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) ```typescript// 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 input id = input.required<string>(); // Computed based on route param userId = computed(() => parseInt(this.id(), 10));}``` Enable with `withComponentInputBinding()`: ```typescript// app.config.tsimport { 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 Search { // Query params as inputs q = input<string>(''); page = input<string>('1'); currentPage = computed(() => parseInt(this.page(), 10));}``` ### With ActivatedRoute (Alternative) ```typescriptimport { 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 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.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 URL return router.createUrlTree(['/login'], { queryParams: { returnUrl: state.url }, });}; // Usage in routes{ path: 'dashboard', component: Dashboard, canActivate: [authGuard],}``` ### Role Guard ```typescriptexport 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 ```typescriptexport 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: ```typescript// 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 ```typescript// 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 ```typescriptimport { Component, inject } from '@angular/core';import { Router } from '@angular/router'; @Component({...})export class Product { 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: Admin, data: { title: 'Admin Dashboard', roles: ['admin'], },} // Access in component@Component({...})export class AdminCmpt { title = input<string>(); // From route data roles = input<string[]>(); // From route data} // Or via ActivatedRouteprivate route = inject(ActivatedRoute);data = toSignal(this.route.data);``` ## Router Events ```typescriptimport { 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](references/routing-patterns.md).Angular Component
The angular-component skill generates modern Angular v20+ standalone components using signal-based inputs and outputs, OnPush change detection, and host binding
Angular Di
Covers Angular v20+ dependency injection patterns with modern inject() syntax instead of constructor injection. Shows provider configuration at root, component,
Angular Directives
Solid reference for building Angular directives the modern way with v20+ patterns. Covers attribute directives using the new `input()` function and `host` prope