路由守卫 #

一、守卫概述 #

路由守卫用于控制路由的访问权限,决定用户是否可以导航到某个路由或离开某个路由。

text
┌─────────────────────────────────────────────────────┐
│                    路由守卫流程                      │
├─────────────────────────────────────────────────────┤
│                                                     │
│  导航开始                                            │
│      ↓                                              │
│  CanLoad        是否可以加载模块                     │
│      ↓                                              │
│  CanActivate    是否可以进入路由                     │
│      ↓                                              │
│  CanActivateChild  是否可以进入子路由                │
│      ↓                                              │
│  Resolve        预加载数据                          │
│      ↓                                              │
│  CanDeactivate  是否可以离开路由                     │
│      ↓                                              │
│  导航完成                                            │
│                                                     │
└─────────────────────────────────────────────────────┘

二、守卫类型 #

守卫 接口 说明
canActivate CanActivateFn 控制是否可以进入路由
canActivateChild CanActivateChildFn 控制是否可以进入子路由
canDeactivate CanDeactivateFn 控制是否可以离开路由
canLoad CanLoadFn 控制是否可以加载模块
canMatch CanMatchFn 控制路由是否匹配

三、CanActivate守卫 #

3.1 创建守卫 #

typescript
import { inject } from '@angular/core';
import { CanActivateFn, Router } from '@angular/router';
import { AuthService } from '../services/auth.service';

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

3.2 使用守卫 #

typescript
import { Routes } from '@angular/router';
import { authGuard } from './guards/auth.guard';

export const routes: Routes = [
  { path: 'login', component: LoginComponent },
  {
    path: 'admin',
    component: AdminComponent,
    canActivate: [authGuard]
  },
  {
    path: 'dashboard',
    component: DashboardComponent,
    canActivate: [authGuard]
  }
];

3.3 完整认证守卫示例 #

typescript
import { inject } from '@angular/core';
import { CanActivateFn, Router, ActivatedRouteSnapshot } from '@angular/router';
import { AuthService } from '../services/auth.service';

export const authGuard: CanActivateFn = (
  route: ActivatedRouteSnapshot,
  state
) => {
  const authService = inject(AuthService);
  const router = inject(Router);
  
  if (!authService.isAuthenticated) {
    router.navigate(['/login'], {
      queryParams: { returnUrl: state.url }
    });
    return false;
  }
  
  const requiredRole = route.data['role'];
  if (requiredRole && !authService.hasRole(requiredRole)) {
    router.navigate(['/forbidden']);
    return false;
  }
  
  return true;
};

四、CanActivateChild守卫 #

4.1 创建守卫 #

typescript
import { inject } from '@angular/core';
import { CanActivateChildFn, Router } from '@angular/router';
import { AuthService } from '../services/auth.service';

export const adminGuard: CanActivateChildFn = (route, state) => {
  const authService = inject(AuthService);
  const router = inject(Router);
  
  if (authService.hasRole('admin')) {
    return true;
  }
  
  router.navigate(['/forbidden']);
  return false;
};

4.2 使用守卫 #

typescript
const routes: Routes = [
  {
    path: 'admin',
    component: AdminComponent,
    canActivateChild: [adminGuard],
    children: [
      { path: 'users', component: AdminUsersComponent },
      { path: 'products', component: AdminProductsComponent },
      { path: 'settings', component: AdminSettingsComponent }
    ]
  }
];

五、CanDeactivate守卫 #

5.1 创建守卫 #

typescript
import { inject } from '@angular/core';
import { CanDeactivateFn } from '@angular/router';
import { Observable } from 'rxjs';

export interface CanComponentDeactivate {
  canDeactivate: () => Observable<boolean> | Promise<boolean> | boolean;
}

export const canDeactivateGuard: CanDeactivateFn<CanComponentDeactivate> = (
  component,
  currentRoute,
  currentState,
  nextState
) => {
  if (component.canDeactivate) {
    return component.canDeactivate();
  }
  return true;
};

5.2 组件实现 #

typescript
import { Component } from '@angular/core';
import { CanComponentDeactivate } from '../guards/can-deactivate.guard';

@Component({
  selector: 'app-user-edit',
  template: `
    <form>
      <input [(ngModel)]="user.name" />
      <button (click)="save()">保存</button>
      <button (click)="cancel()">取消</button>
    </form>
  `
})
export class UserEditComponent implements CanComponentDeactivate {
  user = { name: '' };
  originalName = '';
  
  ngOnInit() {
    this.originalName = this.user.name;
  }
  
  canDeactivate(): boolean {
    if (this.user.name !== this.originalName) {
      return confirm('您有未保存的更改,确定要离开吗?');
    }
    return true;
  }
  
  save() {
    this.originalName = this.user.name;
  }
}

5.3 使用守卫 #

typescript
const routes: Routes = [
  {
    path: 'users/:id/edit',
    component: UserEditComponent,
    canDeactivate: [canDeactivateGuard]
  }
];

六、CanLoad守卫 #

6.1 创建守卫 #

typescript
import { inject } from '@angular/core';
import { CanLoadFn, Route, Router } from '@angular/router';
import { AuthService } from '../services/auth.service';

export const adminLoadGuard: CanLoadFn = (
  route: Route,
  segments
) => {
  const authService = inject(AuthService);
  const router = inject(Router);
  
  if (authService.hasRole('admin')) {
    return true;
  }
  
  router.navigate(['/forbidden']);
  return false;
};

6.2 使用守卫 #

typescript
const routes: Routes = [
  {
    path: 'admin',
    canLoad: [adminLoadGuard],
    loadChildren: () => import('./admin/admin.module')
      .then(m => m.AdminModule)
  }
];

七、CanMatch守卫 #

7.1 创建守卫 #

typescript
import { inject } from '@angular/core';
import { CanMatchFn, Route, Router } from '@angular/router';
import { AuthService } from '../services/auth.service';

export const featureFlagGuard: CanMatchFn = (route: Route, segments) => {
  const authService = inject(AuthService);
  
  if (authService.hasFeature(route.data?.['feature'])) {
    return true;
  }
  
  return false;
};

7.2 使用守卫 #

typescript
const routes: Routes = [
  {
    path: 'new-feature',
    canMatch: [featureFlagGuard],
    data: { feature: 'new-ui' },
    loadComponent: () => import('./new-feature/new-feature.component')
      .then(c => c.NewFeatureComponent)
  },
  {
    path: 'new-feature',
    component: OldFeatureComponent
  }
];

八、组合使用守卫 #

8.1 多个守卫 #

typescript
const routes: Routes = [
  {
    path: 'admin',
    component: AdminComponent,
    canActivate: [authGuard, adminGuard],
    canActivateChild: [adminGuard],
    canDeactivate: [canDeactivateGuard],
    children: [
      {
        path: 'users/:id/edit',
        component: UserEditComponent,
        canDeactivate: [canDeactivateGuard]
      }
    ]
  },
  {
    path: 'reports',
    canLoad: [adminLoadGuard],
    loadChildren: () => import('./reports/reports.module')
      .then(m => m.ReportsModule)
  }
];

8.2 守卫执行顺序 #

text
1. canLoad (仅懒加载模块)
2. canActivate
3. canActivateChild
4. resolve
5. canDeactivate (离开时)

九、守卫返回值 #

9.1 返回类型 #

typescript
// 返回布尔值
return true;
return false;

// 返回UrlTree(重定向)
return router.createUrlTree(['/login']);

// 返回Observable
return authService.checkAuth().pipe(
  map(isAuth => isAuth ? true : router.createUrlTree(['/login']))
);

// 返回Promise
return authService.checkAuth().then(isAuth => isAuth);

9.2 使用Observable #

typescript
import { inject } from '@angular/core';
import { CanActivateFn, Router } from '@angular/router';
import { map, tap } from 'rxjs/operators';
import { AuthService } from '../services/auth.service';

export const authGuard: CanActivateFn = (route, state) => {
  const authService = inject(AuthService);
  const router = inject(Router);
  
  return authService.checkAuthentication().pipe(
    tap(isAuth => {
      if (!isAuth) {
        router.navigate(['/login']);
      }
    })
  );
};

十、权限守卫完整示例 #

10.1 权限服务 #

typescript
@Injectable({ providedIn: 'root' })
export class PermissionService {
  private userPermissions: string[] = [];
  
  constructor(private authService: AuthService) {
    this.loadPermissions();
  }
  
  private loadPermissions() {
    this.authService.currentUser$.subscribe(user => {
      this.userPermissions = user?.permissions || [];
    });
  }
  
  hasPermission(permission: string): boolean {
    return this.userPermissions.includes(permission);
  }
  
  hasAnyPermission(permissions: string[]): boolean {
    return permissions.some(p => this.userPermissions.includes(p));
  }
  
  hasAllPermissions(permissions: string[]): boolean {
    return permissions.every(p => this.userPermissions.includes(p));
  }
}

10.2 权限守卫 #

typescript
import { inject } from '@angular/core';
import { CanActivateFn, Router, ActivatedRouteSnapshot } from '@angular/router';
import { PermissionService } from '../services/permission.service';

export const permissionGuard: CanActivateFn = (
  route: ActivatedRouteSnapshot,
  state
) => {
  const permissionService = inject(PermissionService);
  const router = inject(Router);
  
  const requiredPermissions = route.data['permissions'] as string[];
  const requireAll = route.data['requireAll'] ?? false;
  
  if (!requiredPermissions || requiredPermissions.length === 0) {
    return true;
  }
  
  const hasPermission = requireAll
    ? permissionService.hasAllPermissions(requiredPermissions)
    : permissionService.hasAnyPermission(requiredPermissions);
  
  if (hasPermission) {
    return true;
  }
  
  router.navigate(['/forbidden']);
  return false;
};

10.3 使用权限守卫 #

typescript
const routes: Routes = [
  {
    path: 'users',
    component: UsersComponent,
    canActivate: [permissionGuard],
    data: {
      permissions: ['user:read']
    }
  },
  {
    path: 'users/:id/edit',
    component: UserEditComponent,
    canActivate: [permissionGuard],
    data: {
      permissions: ['user:read', 'user:write'],
      requireAll: true
    }
  }
];

十一、守卫最佳实践 #

11.1 单一职责 #

typescript
// 好的做法:职责单一
export const authGuard: CanActivateFn = (...) => { /* 认证检查 */ };
export const roleGuard: CanActivateFn = (...) => { /* 角色检查 */ };
export const permissionGuard: CanActivateFn = (...) => { /* 权限检查 */ };

// 使用时组合
canActivate: [authGuard, roleGuard, permissionGuard]

11.2 使用函数式守卫 #

typescript
// Angular 17+ 推荐使用函数式守卫
export const authGuard: CanActivateFn = (route, state) => {
  // ...
};

// 不推荐使用类守卫(已弃用)
@Injectable()
export class AuthGuard implements CanActivate {
  canActivate() { }
}

11.3 提供有意义的重定向 #

typescript
export const authGuard: CanActivateFn = (route, state) => {
  const authService = inject(AuthService);
  const router = inject(Router);
  
  if (!authService.isAuthenticated) {
    return router.createUrlTree(['/login'], {
      queryParams: { returnUrl: state.url }
    });
  }
  
  return true;
};

十二、总结 #

守卫 说明 使用场景
canActivate 控制进入路由 认证、权限检查
canActivateChild 控制进入子路由 子路由权限
canDeactivate 控制离开路由 表单未保存提示
canLoad 控制加载模块 模块级权限
canMatch 控制路由匹配 功能开关

下一步:模板驱动表单

最后更新:2026-03-26