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

Angular14中基于其他服务异步数据设置Observable遇错求助

问题:基于OIDC的Auth服务获取用户数据时Observable未定义错误

问题背景

我正在使用基于OIDC的Auth服务获取用户登录后的用户数据,需根据该数据确定用户可见的侧边导航项,此逻辑由StepsService处理。当前方案是创建一个可供StepsService调用的User Observable,利用该Observable的数据设置StepsService的所有必要值,但代码抛出错误“this.authService.getUser() is undefined”,添加空值检查后又出现向数据流传递未定义数据的错误。已在app.module中配置APP_INITIALIZER初始化Auth服务,不确定是否Observable使用有误或问题根源是什么。

相关代码

steps.service.ts

export class StepsService {
  // Instantiate the initial variables that will act as state storage
  sections: Observable<SectionModel[]>;
  currentSection: Observable<SectionModel>;
  stepsInCurrentSection: Observable<StepModel[]>;
  currentStep: Observable<StepModel>;
  currentStepIndex: BehaviorSubject<number> = new BehaviorSubject<number>(0);
  totalStepsInCurrentSection: number;
  totalSections: number;
  currentSectionIndex: BehaviorSubject<number> = new BehaviorSubject<number>(0);
  numberOfCompletedSteps: BehaviorSubject<number> = new BehaviorSubject<number>(0);

  constructor(private authService: AuthService) {
    // Set state variables based on initial steps available
    this.sections = this.authService.getUser().pipe(
      map((user) => {
        console.log('user is: ', user);
        const userRoles: string[] = user.profile['role'];
        return this.getUserSpecificSteps(userRoles);
      }),
      tap((sections) => (this.totalSections = sections.length))
    );
    this.currentSection = combineLatest([this.sections, this.currentSectionIndex]).pipe(
      map((data) => {
        const sections = data[0];
        const index = data[1];
        return sections[index];
      })
    );
    this.stepsInCurrentSection = this.currentSection.pipe(
      map((sections) => sections.steps),
      tap((steps) => (this.totalStepsInCurrentSection = steps.length))
    );
    this.currentStep = combineLatest([this.stepsInCurrentSection, this.currentStepIndex]).pipe(
      map((data) => {
        const steps = data[0];
        const index = data[1];
        return steps[index];
      })
    );
  }

  getUserSpecificSteps(userRoles: string[]) {
    return userRoles?.includes('Admin') ? adminSections : userSections;
  }
...

auth.service.ts

export class AuthService {

  private config = environment;
  private _isAuthenticated: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
  public readonly IsAuthenticated: Observable<boolean> = this._isAuthenticated.asObservable();
  private _userManager: UserManager;
  public _user: User | null;
  public userSubject$: Observable<User>;

  constructor() {
    this._userManager = new UserManager(getClientSettings(this.config));
    this._userManager
      .getUser()
      .then((user: any) => {
        if (user && !user.expired) {
          this._user = user;
          this.userSubject$ = user;
          this._isAuthenticated.next(this.isAuthenticated());
        }
        return user;
      })
      .catch((e) => console.log(e));
    //fires after every user refresh
    this._userManager.events.addUserLoaded((args) => {
      this._userManager
        .getUser()
        .then((user) => {
          this._user = user;
        })
        .catch((e) => console.log(e));
    });
  }

  getUser() {
    return this.userSubject$;
  }
...

问题根源与修复方案

1. 核心问题:userSubject$初始化错误

在AuthService的构造函数中,获取到用户数据后直接将普通user对象赋值给了userSubject$,但userSubject$被定义为Observable<User>类型,普通对象无法直接作为Observable使用。这导致getUser()返回的不是有效Observable,而是未定义值,最终在StepsService调用时抛出错误。

2. 修复步骤

(1)将userSubject$改为BehaviorSubject

在AuthService中,把userSubject$定义为BehaviorSubject,初始化为null,并在获取到用户数据时发送新值:

export class AuthService {
  // ... 其他属性
  public userSubject$: BehaviorSubject<User | null> = new BehaviorSubject<User | null>(null);

  constructor() {
    this._userManager = new UserManager(getClientSettings(this.config));
    this._userManager
      .getUser()
      .then((user: any) => {
        if (user && !user.expired) {
          this._user = user;
          this.userSubject$.next(user); // 发送用户数据到Subject
          this._isAuthenticated.next(this.isAuthenticated());
        } else {
          this.userSubject$.next(null); // 发送null表示无有效用户
        }
        return user;
      })
      .catch((e) => {
        console.log(e);
        this.userSubject$.next(null);
      });
    
    // 用户刷新后更新Subject
    this._userManager.events.addUserLoaded((args) => {
      this._userManager
        .getUser()
        .then((user) => {
          this._user = user;
          this.userSubject$.next(user);
        })
        .catch((e) => {
          console.log(e);
          this.userSubject$.next(null);
        });
    });
  }

  getUser(): Observable<User | null> {
    return this.userSubject$.asObservable(); // 返回Observable而非原始Subject
  }
  // ... 其他方法
}

(2)在StepsService中处理空值

现在getUser()返回的是有效Observable,但可能发送null,需要在数据流中过滤空值并处理潜在的未定义情况:

constructor(private authService: AuthService) {
  this.sections = this.authService.getUser().pipe(
    filter(user => !!user), // 过滤掉null值,只处理有效用户
    map((user) => {
      console.log('user is: ', user);
      const userRoles: string[] = user.profile['role'] || []; // 加默认值避免undefined
      return this.getUserSpecificSteps(userRoles);
    }),
    tap((sections) => (this.totalSections = sections.length))
  );
  // ... 其他Observable逻辑保持不变
}

getUserSpecificSteps(userRoles: string[]) {
  return userRoles.includes('Admin') ? adminSections : userSections;
}

3. 额外优化建议

  • 避免在tap操作中修改同步类属性(如totalSections、totalStepsInCurrentSection),这些值依赖异步数据流,容易导致数据不一致。建议将它们也改为Observable:
    totalSections$: Observable<number> = this.sections.pipe(map(sections => sections.length));
    totalStepsInCurrentSection$: Observable<number> = this.stepsInCurrentSection.pipe(map(steps => steps.length));
    
  • 确保adminSections和userSections是已定义的SectionModel[]类型,避免返回未定义值。

内容的提问来源于stack exchange,提问作者Josh Bangle

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.11 16:50:29