Angular中import()语法懒加载模块路由守卫不生效问题
问题根因
你的懒加载模块路由守卫完全不触发,核心是三个配置错误:
- 懒加载模块的
canActivate/canActivateChild守卫不能直接写在根路由的懒加载父节点上。父路由上的canActivateChild只能管控直接声明在该路由children数组里的子路由,对动态loadChildren导入的模块内部路由无效。 canLoad守卫仅在模块首次懒加载时触发一次,一旦模块被浏览器加载完成,后续访问该模块下的任意路径都不会再执行canLoad逻辑,只在根路由配守卫的话,模块加载后所有子路由就完全裸奔了。- 守卫里的鉴权逻辑没有加流终止操作符,如果你用的是Firebase这类长期持有的用户状态Observable,Angular路由会等待Observable完成才会处理校验结果,流不完成就会一直挂起,看起来就像守卫根本没执行。
修复方案
1. 精简根路由的懒加载配置
根路由的懒加载节点只需要保留canLoad守卫,用来阻止未授权用户加载模块代码即可,删掉无效的canActivate、canActivateChild配置:
export const routes: Routes = [ {path: '', redirectTo: 'groups', pathMatch: 'full'}, {path: '', canActivateChild: [LoggedInGuard], children: [ {component: RoutineListComponent, path: 'routines'}, {component: SessionRunComponent, path: 'run-session', canActivate: [SessionRunGuard]}, {component: RoutineConfigComponent, path: 'routine-config/:id'}, ]}, { path: 'groups', canLoad: [LoggedInGuard], loadChildren: () => import('./exercise-groups/exercise-groups.module') .then(m => m.ExerciseGroupsModule) }, {component: LoginComponent, path: 'login'} ]; @NgModule({ imports: [RouterModule.forRoot(routes, { relativeLinkResolution: 'legacy' })], exports: [RouterModule] }) export class AppRoutingModule { }
2. 在懒加载特性模块内部统一配置子路由守卫
懒加载模块的内部路由归特性模块自己的RouterModule.forChild()管理,你需要在特性模块的路由数组里加一层空路径父路由,把canActivateChild挂在这层父节点上,所有业务路由作为它的子节点,这样模块加载完成后,所有子路由访问都会触发守卫校验:
const routes: Routes = [ { path: '', canActivateChild: [LoggedInGuard], children: [ {path: '', component: ExerciseGroupsMainPageComponent}, {path: 'register-interests', component: RegisterInterestsComponent}, {path: 'available-groups', component: AvailableGroupsPageComponent}, {path: 'upcoming-sessions', component: UpcomingSessionsComponent} ] } ] @NgModule({ declarations: [ LocationsListComponent, LocationGroupsComponent, InterestCardComponent, TimeAfterDirective, RegisterInterestsComponent, AvailabilityCardComponent, ExerciseGroupsMainPageComponent, AvailableGroupsComponent, AvailableGroupsPageComponent, UpcomingSessionsComponent ], imports: [ CommonAngularModule, LayoutModule, AppMaterialModule, FormsModule, FirebaseModule, CommonModule, RouterModule.forChild(routes) ], providers: [ InterestsService, ActivitiesService, AvailabilitiesService ] }) export class ExerciseGroupsModule { }
3. 修正守卫逻辑
抽离公共鉴权方法,不要传null调用canActivateChild,同时给鉴权流加take(1)操作符终止流,避免路由挂起:
import { take, map } from 'rxjs/operators'; @Injectable({ providedIn: 'root' }) export class LoggedInGuard implements CanActivateChild, CanLoad, CanActivate { constructor(private auth: AuthService, private router: Router, private snackBar: MatSnackBar) {} // 抽离公共鉴权逻辑 private checkAuth(): Observable<boolean | UrlTree> { return this.auth.user$.pipe( map(u => { if (!u){ this.router.navigate(['login']); this.snackBar.open('Please login or register to continue', 'OK'); return false; } return true; }), // 触发一次校验后立即终止流,避免路由挂起 take(1) ); } canActivateChild( next: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree { return this.checkAuth(); } canLoad(route: Route, segments: UrlSegment[]): boolean | UrlTree | Observable<boolean | UrlTree> | Promise<boolean | UrlTree> { console.log('canLoad called'); return this.checkAuth(); } canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean | UrlTree | Observable<boolean | UrlTree> | Promise<boolean | UrlTree> { return this.checkAuth(); } }
修复后执行逻辑
- 未登录用户首次访问
/groups/upcoming-sessions时,首先触发根路由上的canLoad守卫,校验不通过直接跳登录页,不会加载ExerciseGroupsModule代码 - 如果用户已经登录、模块已经加载完成,再访问模块下任意子路由,会触发模块内部配置的
canActivateChild守卫做二次校验,登录状态失效就自动跳登录页 - 不会再出现守卫不触发、页面直接裸奔展示的问题
内容的提问来源于stack exchange,提问作者Richard Castle
相关产品推荐
相关产品推荐

