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
相关产品推荐
相关产品推荐

