SKILL.md
readonlyread-only
name
angular-routing
description
在 Angular v20+ 應用程式中實作路由,包含延遲載入、函式守衛、解析器與路由參數。用於導航設定、受保護路由、基於路由的資料載入與巢狀路由。觸發時機:路由設定、加入驗證守衛、實作延遲載入或使用 signal 讀取路由參數。
Angular 路由
在 Angular v20+ 中設定路由,支援延遲載入、函式守衛與基於 signal 的路由參數。
基本設定
// app.routes.ts
import { 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.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: `
<nav>
<a routerLink="/home" routerLinkActive="active">首頁</a>
<a routerLink="/about" routerLinkActive="active">關於</a>
</nav>
<router-outlet />
`,
})
export class App {}
延遲載入
按需載入功能模組:
// app.routes.ts
export const routes: Routes = [
{ path: '', redirectTo: '/home', pathMatch: 'full' },
{ path: 'home', component: Home },
// 延遲載入整個功能
{
path: 'admin',
loadChildren: () => import('./admin/admin.routes').then(m => m.adminRoutes),
},
// 延遲載入單一元件
{
path: 'settings',
loadComponent: () => import('./settings/settings.component').then(m => m.Settings),
},
];
// admin/admin.routes.ts
export const adminRoutes: Routes = [
{ path: '', component: AdminDashboard },
{ path: 'users', component: AdminUsers },
{ path: 'settings', component: AdminSettings },
];
路由參數
使用 Signal 輸入(建議)
// 路由設定
{ path: 'users/:id', component: UserDetail }
// 元件 - 使用 input() 取得路由參數
import { Component, input, computed } from '@angular/core';
@Component({
selector: 'app-user-detail',
template: `
<h1>使用者 {{ id() }}</h1>
`,
})
export class UserDetail {
// 路由參數作為 signal 輸入
id = input.required<string>();
// 基於路由參數的計算值
userId = computed(() => parseInt(this.id(), 10));
}
啟用 withComponentInputBinding():
// app.config.ts
import { provideRouter, withComponentInputBinding } from '@angular/router';
export const appConfig: ApplicationConfig = {
providers: [
provideRouter(routes, withComponentInputBinding()),
],
};
查詢參數
// 路由: /search?q=angular&page=1
@Component({...})
export class Search {
// 查詢參數作為輸入
q = input<string>('');
page = input<string>('1');
currentPage = computed(() => parseInt(this.page(), 10));
}
使用 ActivatedRoute(替代方案)
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);
// 將路由參數轉換為 signal
id = toSignal(
this.route.paramMap.pipe(map(params => params.get('id'))),
{ initialValue: null }
);
// 查詢參數
query = toSignal(
this.route.queryParamMap.pipe(map(params => params.get('q'))),
{ initialValue: '' }
);
}
函式守衛
驗證守衛
// guards/auth.guard.ts
import { 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;
}
// 重新導向至登入頁面,並帶回傳 URL
return router.createUrlTree(['/login'], {
queryParams: { returnUrl: state.url },
});
};
// 在路由中使用
{
path: 'dashboard',
component: Dashboard,
canActivate: [authGuard],
}
角色守衛
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']);
};
};
// 使用方式
{
path: 'admin',
component: Admin,
canActivate: [authGuard, roleGuard(['admin', 'superadmin'])],
}
Can Deactivate 守衛
export interface CanDeactivate {
canDeactivate: () => boolean | Promise<boolean>;
}
export const unsavedChangesGuard: CanDeactivateFn<CanDeactivate> = (component) => {
if (component.canDeactivate()) {
return true;
}
return confirm('您有未儲存的變更。確定要離開嗎?');
};
// 元件實作
@Component({...})
export class Edit implements CanDeactivate {
form = inject(FormBuilder).group({...});
canDeactivate(): boolean {
return !this.form.dirty;
}
}
// 路由
{
path: 'edit/:id',
component: Edit,
canDeactivate: [unsavedChangesGuard],
}
解析器
在路由啟用前預先載入資料:
// resolvers/user.resolver.ts
import { 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);
};
// 路由設定
{
path: 'users/:id',
component: UserDetail,
resolve: { user: userResolver },
}
// 元件 - 透過輸入存取解析後的資料
@Component({...})
export class UserDetail {
user = input.required<User>();
}
巢狀路由
// 父路由包含子路由
export 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>產品</h1>
<router-outlet /> <!-- 子路由在此處渲染 -->
`,
})
export class ProductsLayout {}
程式化導航
import { Component, inject } from '@angular/core';
import { Router } from '@angular/router';
@Component({...})
export class Product {
private router = inject(Router);
// 導航至路由
goToProducts() {
this.router.navigate(['/products']);
}
// 帶參數導航
goToProduct(id: string) {
this.router.navigate(['/products', id]);
}
// 帶查詢參數導航
search(query: string) {
this.router.navigate(['/search'], {
queryParams: { q: query, page: 1 },
});
}
// 相對於當前路由導航
goToEdit() {
this.router.navigate(['edit'], { relativeTo: this.route });
}
// 取代當前歷史記錄
replaceUrl() {
this.router.navigate(['/new-page'], { replaceUrl: true });
}
}
路由資料
// 靜態路由資料
{
path: 'admin',
component: Admin,
data: {
title: '管理後台',
roles: ['admin'],
},
}
// 在元件中存取
@Component({...})
export class AdminCmpt {
title = input<string>(); // 來自路由資料
roles = input<string[]>(); // 來自路由資料
}
// 或透過 ActivatedRoute
private route = inject(ActivatedRoute);
data = toSignal(this.route.data);
路由事件
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);
});
}
}
如需進階模式,請參閱 references/routing-patterns.md。






