路由守衛(Route Guards)

使用場景

路由守衛可以幫助我們控制用戶對特定路由的訪問權限。例如:

  • 希望使用者在登入前只能訪問登入、註冊頁面

  • 希望使用者在登入後無法訪問登入、註冊頁面

  • 防止用戶在表單未保存的情況下離開頁面

  • 確保某些數據在載入視圖前已經準備好

建立路由守衛

使用 Angular CLI 建立路由守衛:

ng generate guard core/guards/auth --functional

選項解釋:

  • CanActivate:決定是否允許用戶訪問特定路由

  • CanActivateChild:控制子路由的訪問權限

  • CanDeactivate:控制從當前路由離開的權限(例如防止用戶未保存表單就離開)

  • CanMatch:決定路由配置是否應被包含在路由匹配過程中

路由守衛參數說明

在 Angular 的路由守衛中,routestate 這兩個參數提供了關於當前導航的重要信息:

1. route (ActivatedRouteSnapshot)

  • 包含當前要激活的路由的訊息快照

  • 可以訪問路由參數 (route.params)

  • 可以獲取路由配置中的數據 (route.data)

  • 可以獲取 URL 中的片段 (route.fragment)

  • 可以訪問查詢參數 (route.queryParams)

  • 可以訪問路由配置 (route.routeConfig)

  • 可以訪問父路由和子路由

2. state (RouterStateSnapshot)

  • 包含整個路由器狀態的快照

  • 提供當前 URL (state.url)

  • 包含整個路由樹的快照

  • 可以訪問所有激活的路由

  • 可以獲取完整的路由信息,包括根路由到當前路由的所有信息

實作範例

檢查用戶是否已登入的守衛

在這個例子中,我們建立兩個守衛:

  1. authGuard:確保只有已登入的用戶可以訪問受保護的頁面

  2. noAuthGuard:確保已登入的用戶無法訪問登入和註冊頁面

// src/app/core/guards/auth.guard.ts
import { CanActivateFn, Router } from '@angular/router';
import { inject } from '@angular/core';

export const authGuard: CanActivateFn = (route, state) => {
  const router = inject(Router);
  const token = localStorage.getItem('auth_token');
  
  if (token) {
    return true;
  }
  
  // 儲存用戶嘗試訪問的 URL,以便在登入後重定向
  localStorage.setItem('redirectUrl', state.url);
  router.navigate(['/login']);
  return false;
};

export const noAuthGuard: CanActivateFn = (route, state) => {
  const router = inject(Router);
  const token = localStorage.getItem('auth_token');
  
  if (!token) {
    return true;
  }
  
  router.navigate(['/event-list']);
  return false;
};

處理路由參數的守衛範例

export const detailGuard: CanActivateFn = (route, state) => {
  const id = route.paramMap.get('id');
  
  // 檢查 ID 是否為有效值
  if (!id || isNaN(+id) || +id <= 0) {
    // 如果 ID 無效,重定向到列表頁面
    const router = inject(Router);
    router.navigate(['/item-list']);
    return false;
  }
  
  return true;
};

在路由配置中使用守衛

// app.routes.ts

export const routes: Routes = [
  { path: '', redirectTo: '/login', pathMatch: 'full' },
  { path: 'login', component: LoginComponent, canActivate: [noAuthGuard] },
  { path: 'register', component: RegisterComponent, canActivate: [noAuthGuard] },
  { path: 'event-list', component: EventListComponent, canActivate: [authGuard] },
  { path: 'event-detail/:id', component: EventDetailComponent, canActivate: [authGuard, detailGuard] },
  // 如果沒有匹配的路由,重定向到登入頁面
  { path: '**', redirectTo: '/login' }
];

處理異步認證狀態

如果你的認證狀態需要從 API 檢查(例如驗證 token 是否仍有效),你可以使用 Observable 來處理:

export const authGuard: CanActivateFn = (route, state) => {
  const authService = inject(AuthService);
  const router = inject(Router);
  
  return authService.isAuthenticated().pipe(
    map(isAuth => {
      if (isAuth) {
        return true;
      }
      
      localStorage.setItem('redirectUrl', state.url);
      router.navigate(['/login']);
      return false;
    })
  );
};

注意事項

  • inject 函數的使用inject 函數只能在注入上下文中使用,不能在模組的頂層直接調用。在路由守衛內部使用是安全的,因為每次函數執行時都會建立新的注入上下文。

  • 組織方式:將相關的守衛函數放在同一個檔案中是合理的做法,尤其是當它們有相似的功能時。這樣可以:

    1. 便於管理和理解相關功能

    2. 共享共同的邏輯或注入的服務

    3. 減少需要導入的檔案數量

  • 路由守衛的順序:如果在一個路由上定義了多個守衛,它們會按照定義的順序依次執行。只有當所有守衛都返回 true 時,用戶才能訪問該路由。

Last updated