Angular 9:解决CanDeactivate守卫与会话超时的冲突问题
解决会话过期时CanDeactivate守卫弹窗阻塞问题
我完全理解你的痛点——会话悄悄过期了,用户想离开页面时却先被未保存修改的弹窗拦住,点击确认后才会触发401跳转,体验确实糟糕。咱们来把这个逻辑理顺,让守卫先校验会话状态,再决定是弹提示还是直接跳登出。
核心思路
利用RxJS的异步流特性,在守卫的canDeactivate方法里先串联会话校验逻辑:
- 先调用后端接口确认会话有效性
- 如果会话失效,直接触发登出流程,跳过弹窗
- 如果会话有效,再执行原有的脏检查和弹窗逻辑
步骤1:改造AuthenticationService的会话校验方法
把原来的Promise改为Observable,方便和守卫里的RxJS操作符结合:
// authentication.service.ts isAuthenticated(): Observable<boolean> { const serverUrl = this.baseURL + 'isValidSession'; return this.http.get(serverUrl, httpOptions).pipe( // 会话有效时返回true map(() => true), catchError((error) => { if (error.status === 401) { // 会话失效返回false return of(false); } // 其他错误场景默认认为会话有效(可根据业务调整) return of(true); }) ); }
步骤2:修改DirtyCheckGuard逻辑
在守卫里先执行会话校验,再分支处理:
// dirty-check.guard.ts canDeactivate(component: DirtyComponent, currentRoute: ActivatedRouteSnapshot): Observable<boolean> | boolean { let formIsDirty = false; // ... 保留你原有设置formIsDirty的逻辑 ... // 先校验会话状态,再处理脏检查 return this.authenticationService.isAuthenticated().pipe( switchMap((isSessionValid) => { if (!isSessionValid) { // 会话失效:直接触发登出,返回false阻止当前导航 this.authenticationService.logout(this.router.url); return of(false); } // 会话有效:执行原有的脏检查逻辑 return component.isDirty$.pipe( switchMap((dirty) => { if ((dirty === false && !formIsDirty)) { this.checkPage(currentRoute.data.origin); return of(true); } else if (dirty === true || (dirty === false && formIsDirty)) { const content = component.isPrinting ? this.contentPrint : this.contentNormal; const confirmText = component.isPrinting ? 'Drucken' : 'Verlassen'; return this.modalService.create({ nzTitle: 'Not saved', nzContent: content, nzFooter: [ { label: 'Abbrechen', onClick: () => this.modalService.closeAll(false) }, { label: confirmText, type: 'primary', onClick: () => { this.checkPage(currentRoute.data.origin); this.modalService.closeAll(true); } } ] }).afterClose.pipe( // 根据弹窗关闭结果返回是否允许导航 map((result) => result === true) ); } return of(true); }), take(1) ); }) ); }
步骤3:优化性能(可选)
为了避免每次守卫触发都调用后端接口,可以在AuthenticationService里缓存会话状态:
// authentication.service.ts private sessionValid$ = new BehaviorSubject<boolean>(true); isAuthenticated(): Observable<boolean> { const serverUrl = this.baseURL + 'isValidSession'; return this.http.get(serverUrl, httpOptions).pipe( tap(() => this.sessionValid$.next(true)), map(() => true), catchError((error) => { if (error.status === 401) { this.sessionValid$.next(false); return of(false); } this.sessionValid$.next(true); return of(true); }) ); } // 提供缓存的会话状态 getSessionValid$(): Observable<boolean> { return this.sessionValid$.asObservable(); }
同时在HttpInterceptor里收到401时更新缓存状态:
// interceptor.ts if (error.status === 401 ) { this.authenticationService.sessionValid$.next(false); this.authenticationService.logout(requestRoute); }
这样守卫可以先检查缓存状态,只有缓存有效时才去调用后端接口,减少不必要的请求。
关键注意点
- 确保
isValidSession接口在会话过期时会返回401,符合BasicAuth的认证逻辑 - 模态框的
afterClose要正确传递导航结果(true/false),让Angular能正确判断是否允许页面跳转 - 如果你的NzModal版本不同,
afterClose的返回值逻辑可能需要微调,以匹配实际的按钮点击结果
内容的提问来源于stack exchange,提问作者D. K.
相关产品推荐
相关产品推荐

