angular-routing
Installation
SKILL.md
Angular Routing
Modern routing patterns with standalone components, functional guards, and signal-based parameter access.
Basic Route Configuration
// app.routes.ts
import { Routes } from '@angular/router';
export const routes: Routes = [
{
path: '',
loadComponent: () => import('./home/home.component')
.then(m => m.HomeComponent)
},
{
path: 'about',
loadComponent: () => import('./about/about.component')
.then(m => m.AboutComponent)
},
{
path: 'users/:id',
loadComponent: () => import('./user/user-detail.component')
.then(m => m.UserDetailComponent)
},
{
path: '**',
redirectTo: ''
}
];
App Configuration
// 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)
]
};
Lazy Loading
Feature Routes
// feature.routes.ts
import { Routes } from '@angular/router';
export const FEATURE_ROUTES: Routes = [
{
path: '',
loadComponent: () => import('./feature-list.component')
.then(m => m.FeatureListComponent)
},
{
path: ':id',
loadComponent: () => import('./feature-detail.component')
.then(m => m.FeatureDetailComponent)
}
];
// app.routes.ts
export const routes: Routes = [
{
path: 'features',
loadChildren: () => import('./features/feature.routes')
.then(m => m.FEATURE_ROUTES)
}
];
Accessing Route Parameters
Using ActivatedRoute with Signals
import { Component, inject } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { toSignal } from '@angular/core/rxjs-interop';
import { map } from 'rxjs/operators';
@Component({
selector: 'app-user-detail',
template: `
<h1>User ID: {{ userId() }}</h1>
<h2>Tab: {{ tab() }}</h2>
`
})
export class UserDetailComponent {
private route = inject(ActivatedRoute);
// Route params as signal
userId = toSignal(
this.route.params.pipe(map(params => params['id'])),
{ initialValue: '' }
);
// Query params as signal
tab = toSignal(
this.route.queryParams.pipe(map(params => params['tab'] || 'overview')),
{ initialValue: 'overview' }
);
}
Using input() with Route Data (Angular 16+)
import { Component, input } from '@angular/core';
// Route configuration
{
path: 'users/:id',
loadComponent: () => import('./user-detail.component')
.then(m => m.UserDetailComponent)
}
@Component({
selector: 'app-user-detail',
template: `
<h1>User ID: {{ id() }}</h1>
`
})
export class UserDetailComponent {
// Automatically binds to route param 'id'
id = input.required<string>();
}
Functional Guards
canActivate Guard
// auth.guard.ts
import { inject } from '@angular/core';
import { Router, CanActivateFn } from '@angular/router';
import { AuthService } from './auth.service';
export const authGuard: CanActivateFn = (route, state) => {
const authService = inject(AuthService);
const router = inject(Router);
if (authService.isAuthenticated()) {
return true;
}
// Redirect to login
return router.createUrlTree(['/login'], {
queryParams: { returnUrl: state.url }
});
};
// Usage in routes
{
path: 'profile',
loadComponent: () => import('./profile.component')
.then(m => m.ProfileComponent),
canActivate: [authGuard]
}
canActivateChild Guard
export const adminGuard: CanActivateFn = () => {
const authService = inject(AuthService);
return authService.hasRole('admin');
};
{
path: 'admin',
canActivateChild: [adminGuard],
children: [
{
path: 'users',
loadComponent: () => import('./admin-users.component')
.then(m => m.AdminUsersComponent)
},
{
path: 'settings',
loadComponent: () => import('./admin-settings.component')
.then(m => m.AdminSettingsComponent)
}
]
}
canDeactivate Guard
import { CanDeactivateFn } from '@angular/router';
export interface CanComponentDeactivate {
canDeactivate: () => boolean | Promise<boolean>;
}
export const unsavedChangesGuard: CanDeactivateFn<CanComponentDeactivate> = (
component
) => {
return component.canDeactivate
? component.canDeactivate()
: true;
};
// Component
@Component({
selector: 'app-form',
template: `...`
})
export class FormComponent implements CanComponentDeactivate {
hasUnsavedChanges = signal(false);
canDeactivate(): boolean {
if (this.hasUnsavedChanges()) {
return confirm('You have unsaved changes. Do you want to leave?');
}
return true;
}
}
// Route
{
path: 'edit',
loadComponent: () => import('./form.component')
.then(m => m.FormComponent),
canDeactivate: [unsavedChangesGuard]
}
Functional Resolvers
import { ResolveFn } from '@angular/router';
import { inject } from '@angular/core';
import { UserService } from './user.service';
export const userResolver: ResolveFn<User> = (route, state) => {
const userService = inject(UserService);
const id = route.paramMap.get('id')!;
return userService.getUser(id);
};
// Route configuration
{
path: 'users/:id',
loadComponent: () => import('./user-detail.component')
.then(m => m.UserDetailComponent),
resolve: {
user: userResolver
}
}
// Component
@Component({
selector: 'app-user-detail',
template: `
@if (user(); as userData) {
<h1>{{ userData.name }}</h1>
}
`
})
export class UserDetailComponent {
private route = inject(ActivatedRoute);
user = toSignal(
this.route.data.pipe(map(data => data['user'])),
{ initialValue: null }
);
}
Navigation
Programmatic Navigation
import { Component, inject } from '@angular/core';
import { Router } from '@angular/router';
@Component({
selector: 'app-navigation',
template: `
<button (click)="goToUser(123)">Go to User</button>
<button (click)="goBack()">Go Back</button>
`
})
export class NavigationComponent {
private router = inject(Router);
goToUser(id: number) {
// Navigate to route
this.router.navigate(['/users', id]);
// With query params
this.router.navigate(['/users', id], {
queryParams: { tab: 'profile' }
});
// Navigate by URL
this.router.navigateByUrl(`/users/${id}`);
}
goBack() {
// Go back in history
window.history.back();
}
}
RouterLink Directive
@Component({
selector: 'app-nav',
standalone: true,
imports: [RouterLink, RouterLinkActive],
template: `
<nav>
<a routerLink="/" routerLinkActive="active"
[routerLinkActiveOptions]="{exact: true}">
Home
</a>
<a [routerLink]="['/users', userId]" routerLinkActive="active">
User Profile
</a>
<a [routerLink]="['/settings']"
[queryParams]="{tab: 'notifications'}"
routerLinkActive="active">
Settings
</a>
</nav>
`
})
export class NavComponent {
userId = 123;
}
Child Routes
// Parent component
@Component({
selector: 'app-dashboard',
standalone: true,
imports: [RouterOutlet],
template: `
<div class="dashboard">
<nav><!-- navigation --></nav>
<router-outlet />
</div>
`
})
export class DashboardComponent {}
// Routes
{
path: 'dashboard',
loadComponent: () => import('./dashboard.component')
.then(m => m.DashboardComponent),
children: [
{
path: '',
redirectTo: 'overview',
pathMatch: 'full'
},
{
path: 'overview',
loadComponent: () => import('./overview.component')
.then(m => m.OverviewComponent)
},
{
path: 'analytics',
loadComponent: () => import('./analytics.component')
.then(m => m.AnalyticsComponent)
}
]
}
Route Data
// Static route data
{
path: 'admin',
loadComponent: () => import('./admin.component')
.then(m => m.AdminComponent),
data: {
title: 'Admin Panel',
requiredRole: 'admin',
breadcrumb: 'Administration'
}
}
// Access route data
@Component({
selector: 'app-admin',
template: `<h1>{{ title() }}</h1>`
})
export class AdminComponent {
private route = inject(ActivatedRoute);
title = toSignal(
this.route.data.pipe(map(data => data['title'])),
{ initialValue: '' }
);
}
Router Events
import { Component, inject } from '@angular/core';
import { Router, NavigationEnd } from '@angular/router';
import { filter } from 'rxjs/operators';
@Component({
selector: 'app-root',
template: `<router-outlet />`
})
export class AppComponent {
private router = inject(Router);
constructor() {
// Listen to navigation events
this.router.events.pipe(
filter(event => event instanceof NavigationEnd)
).subscribe((event: NavigationEnd) => {
console.log('Navigated to:', event.url);
// Track page views, update breadcrumbs, etc.
});
}
}
Advanced Patterns
Preloading Strategy
import { ApplicationConfig } from '@angular/core';
import {
provideRouter,
PreloadAllModules,
withPreloading
} from '@angular/router';
export const appConfig: ApplicationConfig = {
providers: [
provideRouter(
routes,
withPreloading(PreloadAllModules)
)
]
};
Custom Preloading Strategy
import { Injectable } from '@angular/core';
import { PreloadingStrategy, Route } from '@angular/router';
import { Observable, of } from 'rxjs';
@Injectable({ providedIn: 'root' })
export class CustomPreloadingStrategy implements PreloadingStrategy {
preload(route: Route, load: () => Observable<any>): Observable<any> {
// Only preload routes with data.preload = true
return route.data?.['preload'] ? load() : of(null);
}
}
// Usage
{
path: 'important',
loadComponent: () => import('./important.component')
.then(m => m.ImportantComponent),
data: { preload: true }
}
Best Practices
- Use lazy loading for better initial load performance
- Prefer functional guards over class-based guards
- Use signal-based parameter access with toSignal()
- Implement canDeactivate for unsaved changes
- Use resolvers to pre-fetch data before navigation
- Keep route configuration flat when possible
- Use route data for static configuration
- Implement proper error handling in guards/resolvers
Resources
- Angular Routing Guide: https://angular.dev/guide/routing
- Router API: https://angular.dev/api/router/Router
Weekly Installs
2
Repository
simon-jarillo/p…a-skillsFirst Seen
Jan 26, 2026
Security Audits
Installed on
claude-code2
codex1
github-copilot1
gemini-cli1