路由守卫 #
一、守卫概述 #
路由守卫用于控制路由的访问权限,决定用户是否可以导航到某个路由或离开某个路由。
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