You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用Angular Signals实现登录状态路由守卫的问题

Angular Signals 认证场景下路由守卫异步问题的解决方案

现有实现的核心问题

  1. getLoggedInUser方法设计缺陷:内部订阅异步请求后,直接返回包含Signal当前值的Observable,这个值是初始的undefined,并非请求完成后的用户数据;同时未处理订阅的内存泄漏风险。
  2. 路由守卫未处理异步状态:仅检查Signal的当前值,完全忽略了用户数据正在加载的状态,导致守卫执行时请求还未完成,直接判定未认证。
  3. 初始化时机不当:在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.13 12:04:53