Angular 路由进阶:路由守卫、懒加载、动态路由、路由传参避坑合集
Angular 路由远不止 router-outlet 那么简单。在中大型项目中,路由往往是坑最多、问题最隐蔽、重构成本最高的地方。
本文不聊基础用法,只聚焦 进阶场景 + 实战坑点,覆盖:
- ✅ 路由守卫(CanActivate / CanMatch / CanDeactivate)
- ✅ 懒加载(NgModule & Standalone 双模式)
- ✅ 动态路由(参数监听、复用陷阱)
- ✅ 路由传参(query / param / state 全对比)
- ✅ 常见异常与调试技巧
一、路由守卫:别再用错生命周期了
1️⃣ 四大守卫该怎么选?
| 守卫 | 触发时机 | 典型场景 |
|---|---|---|
CanActivate |
进入路由前 | 登录校验 |
CanMatch |
匹配路由前 | 权限控制懒加载 |
CanDeactivate |
离开路由前 | 表单未保存提示 |
Resolve |
进入路由前(已废弃) | 预取数据(改用 withComponentInputBinding) |
📌 Angular v16+ 推荐趋势
Resolve→ 路由参数绑定 + effectCanActivate→ 更轻量CanMatch→ 权限路由首选
2️⃣ CanActivate:最常见的坑
❌ 错误示例
typescript
canActivate(): boolean {
return !!localStorage.getItem('token');
}
问题
- 同步返回,无法处理异步登录
- 刷新页面直接失效
✅ 正确写法(返回 Observable / Promise)
kotlin
canActivate(): Observable<boolean> {
return this.authService.isLoggedIn$.pipe(
take(1),
map(loggedIn => {
if (!loggedIn) {
this.router.navigate(['/login']);
}
return loggedIn;
})
);
}
📌 关键点
- 一定要
take(1),否则路由卡死 - 用
router.navigate而不是location.href
3️⃣ CanMatch:权限路由的最佳实践
比 CanActivate 更适合控制"是否加载某个路由"
css
const routes: Routes = [
{
path: 'admin',
loadComponent: () => import('./admin.component'),
canMatch: [AuthGuard]
}
];
✅ 优势
- 路由未命中,直接不加载 chunk
- 权限逻辑更靠前
4️⃣ CanDeactivate:表单防丢数据的正确姿势
typescript
export interface CanComponentDeactivate {
canDeactivate: () => Observable<boolean> | Promise<boolean> | boolean;
}
@Injectable()
export class PendingChangesGuard implements CanDeactivate<CanComponentDeactivate> {
canDeactivate(component: CanComponentDeactivate) {
return component.canDeactivate ? component.canDeactivate() : true;
}
}
组件中:
javascript
canDeactivate() {
return this.form.dirty ? confirm('有未保存的更改,确定离开吗?') : true;
}
📌 坑点
- 刷新页面不会触发
- 浏览器关闭由
beforeunload处理
二、懒加载:NgModule vs Standalone
1️⃣ NgModule 懒加载(老项目)
css
{
path: 'user',
loadChildren: () =>
import('./user/user.module').then(m => m.UserModule)
}
✅ 稳定
❌ 多一层 Module
2️⃣ Standalone 懒加载(推荐 ✅)
css
{
path: 'user',
loadComponent: () =>
import('./user/user.component').then(m => m.UserComponent)
}
📌 优点
- 无 Module
- chunk 更小
- 更符合未来 Angular 方向
3️⃣ 懒加载 + 守卫组合(高级)
css
{
path: 'dashboard',
loadComponent: () => import('./dashboard.component'),
canMatch: [AuthGuard],
canActivate: [RoleGuard]
}
执行顺序:
canMatch → canActivate → 组件初始化
三、动态路由:参数监听的 3 个陷阱
1️⃣ 错误监听方式(最常见)
javascript
ngOnInit() {
console.log(this.route.snapshot.paramMap.get('id'));
}
❌ 问题
- 同一组件复用(路由参数变化)时不触发
2️⃣ 正确方式:订阅 Observable
ini
ngOnInit() {
this.route.paramMap.subscribe(params => {
this.id = params.get('id');
this.loadData();
});
}
✅ 适用场景
- 列表 → 详情 → 另一个详情
3️⃣ 组件复用 + 手动销毁(重要)
typescript
private destroy$ = new Subject<void>();
ngOnInit() {
this.route.paramMap
.pipe(takeUntil(this.destroy$))
.subscribe(...)
}
ngOnDestroy() {
this.destroy$.next();
this.destroy$.complete();
}
📌 不销毁 = 内存泄漏
4️⃣ 使用 withComponentInputBinding(Angular v16+ ✅)
scss
// app.config.ts
provideRouter(routes, withComponentInputBinding())
组件中:
java
@Input() id!: string;
✅ 优势
- 响应式
- 无需手动订阅
- 与 Angular 新方向一致
四、路由传参:query / param / state 全对比
1️⃣ 三种方式对比
| 方式 | 是否显示在 URL | 刷新是否保留 | 推荐场景 |
|---|---|---|---|
| path param | ✅ | ✅ | 资源 ID |
| query param | ✅ | ✅ | 筛选、分页 |
| state | ❌ | ❌ | 临时数据 |
2️⃣ Path Param(最常用)
css
{ path: 'user/:id' }
kotlin
this.router.navigate(['/user', 123]);
kotlin
this.route.snapshot.paramMap.get('id');
✅ 适合
- 详情页
- SEO 友好
3️⃣ Query Param(容易踩坑)
❌ 错误写法
kotlin
this.router.navigate(['/list?page=2']);
✅ 正确写法
css
this.router.navigate(['/list'], {
queryParams: { page: 2 }
});
📌 坑点
-
queryParamsHandlingpreserve:保留旧参数merge:合并新旧参数
css
this.router.navigate(['/list'], {
queryParams: { page: 2 },
queryParamsHandling: 'merge'
});
4️⃣ State(很多人不知道)
css
this.router.navigate(['/success'], {
state: { from: 'checkout' }
});
获取:
ini
window.history.state;
✅ 适合
- 临时状态
- 不需要 URL 暴露的数据
❌ 刷新即丢失
五、路由复用策略(进阶但慎用)
1️⃣ 为什么需要?
- 表单页切换不销毁
- Tab 页缓存
2️⃣ 简单示例
typescript
export class CustomReuseStrategy implements RouteReuseStrategy {
shouldDetach() { return false; }
store() {}
shouldAttach() { return false; }
retrieve() { return null; }
shouldReuseRoute() { return false; }
}
📌 强烈建议
- 不要自己造轮子
- 考虑
ngrx/component-store或signal
六、常见异常 & 调试技巧
❌ 1. Error: NG04002: Cannot match any routes
✅ 排查顺序:
- 路径是否多
/ - 是否忘记
pathMatch: 'full' - 是否守卫返回
false
❌ 2. 路由跳转但页面不刷新
✅ 原因:
- 同一组件复用
- 使用
snapshot而非订阅
❌ 3. 懒加载 chunk 404
✅ 检查:
base hrefdeployUrloutputPath
七、最佳实践总结(强烈建议收藏)
✅ 守卫
- 登录校验 →
CanActivate - 权限控制 →
CanMatch - 表单保护 →
CanDeactivate
✅ 懒加载
- 新项目 →
loadComponent - 老项目 → 渐进迁移
✅ 参数
- ID → path param
- 筛选 → query param
- 临时数据 → state
✅ 订阅
- 永远
takeUntil - 新项目优先
withComponentInputBinding
八、结语:路由是 Angular 的"神经系统"
路由设计得好,项目结构自然清晰;
路由设计得差,后期重构全是坑。