angular-routing

angular-routing

熱門

在 Angular v20+ 應用程式中實作路由,包含延遲載入、函式守衛、解析器與路由參數。用於導航設定、受保護路由、基於路由的資料載入與巢狀路由。觸發時機:路由設定、加入驗證守衛、實作延遲載入或使用 signal 讀取路由參數。

596星標
70分支
更新於 2026/3/23
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