Claude Agent Skill · by Analogjs

Angular Routing

Angular Routing skill enables developers to configure navigation in Angular v20+ applications using lazy loading, functional guards, resolvers, and signal-based

Install
Terminal · npx
$npx skills add https://github.com/analogjs/angular-skills --skill angular-routing
Works with Paperclip

How 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.

S
SaaS FactoryPaired

Pre-configured AI company — 18 agents, 18 skills, one-time purchase.

$27$59
Explore pack
Source file
SKILL.md395 lines
Expand
---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).