使用Angular Signals实现登录状态路由守卫的问题
Angular Signals 认证场景下路由守卫异步问题的解决方案
现有实现的核心问题
getLoggedInUser方法设计缺陷:内部订阅异步请求后,直接返回包含Signal当前值的Observable,这个值是初始的undefined,并非请求完成后的用户数据;同时未处理订阅的内存泄漏风险。- 路由守卫未处理异步状态:仅检查Signal的当前值,完全忽略了用户数据正在加载的状态,导致守卫执行时请求还未完成,直接判定未认证。
- 初始化时机不当:在
AppComponent的ngOnInit中触发用户加载,这个时机晚于路由守卫的执行,导致守卫无法获取到异步加载后的用户数据。
重构方案
1. 重构AuthService:管理异步状态与Signal
将用户状态设计为包含加载状态和用户数据的Signal,在服务初始化时自动触发用户加载逻辑,确保路由守卫执行时状态已处于加载或完成状态:
import { inject, Injectable, signal } from '@angular/core'; import { UserInterface } from '../interfaces/user.interface'; import { UserService } from './user.service'; import { catchError, of, tap } from 'rxjs'; @Injectable({ providedIn: 'root' }) export class AuthService { private userService = inject(UserService); // 用Signal存储完整状态:加载中、用户数据 currentUserState = signal<{ loading: boolean, user: UserInterface | null }>({ loading: true, user: null }); constructor() { this.loadCurrentUser(); } private loadCurrentUser(): void { const token = localStorage.getItem('token'); // 无令牌直接标记加载完成,用户为null if (!token) { this.currentUserState.set({ loading: false, user: null }); return; } // 有令牌时请求用户数据,更新Signal状态 this.userService.getUser().pipe( catchError(() => { // 请求失败时清除无效令牌 localStorage.removeItem('token'); return of(null); }), tap(user => { this.currentUserState.set({ loading: false, user: user ?? null }); }) ).subscribe(); } }
2. 改进路由守卫:处理异步加载状态
路由守卫需要支持异步判断逻辑,当用户数据还在加载中时,等待状态更新后再做出判定:
import { CanActivateFn, Router } from '@angular/router'; import { inject } from '@angular/core'; import { AuthService } from './auth.service'; import { map, take } from 'rxjs'; import { toObservable } from '@angular/core/rxjs-interop'; export const isAuthenticatedGuard: CanActivateFn = () => { const authService = inject(AuthService); const router = inject(Router); const currentState = authService.currentUserState(); // 已加载完成:直接判断用户是否存在 if (!currentState.loading) { return currentState.user !== null || router.createUrlTree(['/login']); } // 加载中:转为Observable等待状态更新,仅取一次结果 return toObservable(authService.currentUserState).pipe( take(1), map(state => { return state.user !== null ? true : router.createUrlTree(['/login']); }) ); };
3. 移除冗余代码
删除AppComponent中ngOnInit内的getLoggedInUser调用,因为AuthService的构造函数已经自动执行了用户加载逻辑,避免重复请求。
关键注意点
- 始终在Signal中维护加载状态:避免仅存储用户数据,否则无法区分“未加载”和“无用户”两种状态。
- 路由守卫要兼容异步场景:利用
toObservable将Signal转为Observable,等待状态更新后再做判断。 - 服务初始化时触发数据加载:确保路由守卫执行时,用户数据的加载逻辑已经启动,不会出现状态未初始化的情况。
内容的提问来源于stack exchange,提问作者Brian
相关产品推荐
相关产品推荐

