Skip to content

替换 Angular 弃用的 CanActivate 接口

问题陈述

自 Angular v15.2.0 起,CanActivateCanActivateChild 接口正式被标记为废弃。当开发者从早期版本(如 v15.1.4)升级时,IDE 会显示弃用警告。根据 Angular 官方文档,框架建议采用纯 JavaScript 函数替代传统的类守卫实现。

典型废弃的守卫实现如下:

typescript
export class AuthGuard implements CanActivate, CanActivateChild {
  constructor(private authService: AuthenticationService) {}

  canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree {
    return this.authService.checkLogin().pipe(
      map(() => true),
      catchError(() => {
        this.router.navigate(['route-to-fallback-page']);
        return of(false);
      })
    );
  }

  canActivateChild(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
    return this.canActivate(route, state);
  }
}

注意

尽管在 Angular v18 中 CanActivate取消弃用,官方仍推荐优先使用函数式守卫(相关 PR)。本文同时涵盖新旧版本的最佳实践。

核心解决方案

函数式守卫(推荐方式)

使用 CanActivateFn 类型定义函数式守卫,通过 inject() 实现依赖注入:

typescript
import { CanActivateFn, CanActivateChildFn } from '@angular/router';
import { inject } from '@angular/core';

export const authGuard: CanActivateFn = (
  route: ActivatedRouteSnapshot,
  state: RouterStateSnapshot
) => {
  const authService = inject(AuthenticationService);
  const router = inject(Router);

  return authService.checkLogin().pipe(
    map(() => true),
    catchError(() => router.createUrlTree(['route-to-fallback-page']))
  );
};

export const authChildGuard: CanActivateChildFn = (route, state) => 
  authGuard(route, state);

在路由配置中的使用方式:

typescript
{
  path: 'protected',
  canActivate: [authGuard], // 直接引用函数
  component: ProtectedComponent
}

关键说明

  1. inject() 必须在注入上下文中调用(如构造函数或传递给 Angular 核心功能的函数)
  2. 使用 createUrlTree 优于直接导航(router.navigate),确保守卫的声明式特性
  3. 函数式守卫天然支持组合功能(如多个守卫级联)

转换类守卫的辅助方案

若需要复用现有类守卫逻辑,可采用 Angular 提供的转换函数:

typescript
import { mapToCanActivate } from '@angular/router';

@Injectable({ providedIn: 'root' })
export class LegacyAuthGuard {
  canActivate(route: ActivatedRouteSnapshot) {
    // 原守卫逻辑
  }
}

// 路由配置
const routes: Routes = [
  {
    path: 'admin',
    canActivate: mapToCanActivate([LegacyAuthGuard]), // 转换为函数式
    component: AdminComponent
  }
];
其他转换方法

若不想修改路由配置,可在类外部包装函数:

typescript
export const AuthGuard: CanActivateFn = (next, state) => 
  inject(LegacyAuthGuard).canActivate(next, state);

此方法保持路由配置不变:canActivate: [AuthGuard]

类守卫的现代用法(仅限 v18+)

从 Angular v18 开始,类守卫可继续使用(取消弃用),推荐采用函数式调用:

typescript
{
  path: 'dashboard',
  canActivate: [
    (next: ActivatedRouteSnapshot, state: RouterStateSnapshot) => 
      inject(AuthGuard).canActivate(next, state)
  ],
  component: DashboardComponent
}

架构设计与最佳实践

代码组织建议

对复杂守卫逻辑,推荐使用命名空间组织相关函数:

typescript
// auth.guard.ts
export namespace AuthGuard {
  export const canActivate: CanActivateFn = (route, state) => {
    /* 实现 */
  };
  
  export const canActivateChild: CanActivateChildFn = (route, state) => 
    canActivate(route, state);
}

// 路由配置
{ 
  path: 'profile',
  canActivate: [AuthGuard.canActivate],
  canDeactivate: [AuthGuard.canDeactivate] 
}

各方案对比

方案适用版本优点缺点
纯函数式守卫v15.2+✅ 无样板代码
✅ 组合性强
❌ 需重构原有实现
mapToCanActivatev16+✅ 无缝集成现有类❌ 中间转换层
注入类方法所有版本✅ 最小改动❌ 依赖注入冗余
类守卫直接使用v18+✅ 完全兼容历史代码⚠️ 官方不推荐首选

迁移路径建议

  1. 评估依赖:识别守卫中的所有依赖项(服务、路由器等)
  2. 选择策略
    • 新项目/组件 → 采用纯函数式
    • 大型遗留系统 → 使用 mapToCanActivate
    • v18项目 → 渐进式迁移到函数式
  3. 重构测试
    typescript
    // 改造后更易测试的守卫
    it('应重定向未认证用户', async () => {
      const mockAuth = { checkLogin: () => throwError('') };
      const result = await authGuard(null!, null!, {
        authService: mockAuth as any
      });
      expect(result.toString()).toBe('/login');
    });

为什么转向函数式?

  1. 减少样板代码:消除类定义的冗余
  2. 提升测试灵活性:无需依赖 Angular 测试工具
  3. 更好的Tree-shaking:函数更易被编译器优化
  4. 优化运行时性能:消除类实例化的开销

总结

Angular 对路由守卫的演进体现了向函数式编程的转变:

  • 现代方法:优先使用 CanActivateFn 函数式守卫
  • 迁移工具:借助 inject()mapToCanActivate 实现平滑过渡
  • 版本适配:v18+项目可使用类守卫,但函数式仍是未来方向

建议参照官方路由守卫升级指南系统执行重构工作。