Angular12中将OKTA返回的Observable转为普通字符串变量
问题根源
你当前实现的核心问题有两个:
- 重复对
authState$流做相同的过滤、映射操作,产生了大量冗余Observable,性能差且维护成本高 - 错误使用
of()操作符将多个未订阅的Observable直接包装为静态值返回,最终拿到的User对象所有字段都是Observable类型,才必须依赖async管道才能渲染值。你写的stringName订阅逻辑完全无效,subscribe返回的是订阅实例而非字符串值。
正确实现方案
1. 重构UserService
服务内只订阅一次authState$流,内部维护普通类型的User对象,对外暴露同步访问方法,同时保留Observable版本兼容特殊场景:
import { Injectable, OnDestroy, Inject } from '@angular/core'; import { OKTA_AUTH, OktaAuthStateService } from '@okta/okta-angular'; import { filter, Subject, takeUntil } from 'rxjs'; import { AuthState, OktaAuth } from '@okta/okta-auth-js'; import { User } from './data/user.model'; @Injectable({ providedIn: 'root' }) export class UserService implements OnDestroy { // 普通类型的用户对象,直接存字符串值,不需要Observable包装 user: User = { email: '', name: '', givenName: '', familyName: '', accessToken: '', tenancy: [], tenantIds: [] }; private destroy$ = new Subject<void>(); constructor( private _oktaAuthStateService: OktaAuthStateService, @Inject(OKTA_AUTH) private _oktaAuth: OktaAuth ) { // 服务初始化时就订阅认证状态流,只订阅一次 this._oktaAuthStateService.authState$ .pipe( filter((authState: AuthState) => !!authState), takeUntil(this.destroy$) ) .subscribe((authState: AuthState) => { // 认证状态更新时,直接给普通用户对象赋值,全是字符串/普通类型 this.user = { email: authState.idToken?.claims.email ?? '', name: authState.idToken?.claims.name ?? '', givenName: authState.idToken?.claims.given_name ?? '', familyName: authState.idToken?.claims.family_name ?? '', accessToken: authState.accessToken?.accessToken ?? '', tenancy: [], tenantIds: [] }; }); } ngOnDestroy(): void { // 服务销毁时取消订阅,防止内存泄漏 this.destroy$.next(); this.destroy$.complete(); } }
2. 修改AppComponent
把所有用户相关的Observable类型字段改成普通类型,直接读取服务里的同步用户对象即可,不需要手动订阅一堆流:
import { Component, Inject, OnInit } from '@angular/core'; import { Router } from '@angular/router'; import { OktaAuthStateService, OKTA_AUTH } from '@okta/okta-angular'; import { AuthState, OktaAuth } from '@okta/okta-auth-js'; import { filter, map, Observable } from 'rxjs'; import { UserService } from './user.service'; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'] }) export class AppComponent implements OnInit { title = 'okta-angular-quickstart'; public isAuthenticated$!: Observable<boolean>; // 所有用户字段全部改成普通类型,不需要Observable包装 name = ''; email = ''; givenName = ''; familyName = ''; accessToken = ''; constructor( private _router: Router, private _oktaStateService: OktaAuthStateService, @Inject(OKTA_AUTH) private _oktaAuth: OktaAuth, private userService: UserService ) { } public ngOnInit(): void { this.isAuthenticated$ = this._oktaStateService.authState$.pipe( filter((s: AuthState) => !!s), map((s: AuthState) => s.isAuthenticated ?? false) ); // 认证状态变化时同步更新组件内的普通变量,视图会自动触发变更检测 this._oktaStateService.authState$ .pipe(filter((s: AuthState) => !!s)) .subscribe(() => { this.name = this.userService.user.name; this.email = this.userService.user.email; this.givenName = this.userService.user.givenName; this.familyName = this.userService.user.familyName; this.accessToken = this.userService.user.accessToken; }); } public async signIn() : Promise<void> { await this._oktaAuth.signInWithRedirect().then( _ => this._router.navigate(['/profile']) ); } public async signOut(): Promise<void> { await this._oktaAuth.signOut(); } }
3. 修改模板文件
去掉所有用户字段的async管道,直接绑定普通变量即可:
<h1>Hello world - here's where you are</h1> <p>Name: {{name}}</p> <p>Email: {{email}}</p> <p>FamilyName: {{familyName}}</p> <p>GivenName: {{givenName}}</p> <p>AccessToken: {{accessToken}}</p> <p>Is Authenticated: {{isAuthenticated$ | async}}</p>
实现说明
- 整个实现只对
authState$做了最少次数的订阅,没有冗余的流操作,性能更好 - 所有用户属性都是普通字符串类型,不需要async管道就能直接在模板、业务逻辑里使用
- 初始状态所有用户字段为空字符串,不会出现空值报错,Okta完成认证状态校验后会自动更新值,视图同步刷新
- 加入了订阅销毁逻辑,不会产生内存泄漏
内容的提问来源于stack exchange,提问作者K2D2
相关产品推荐
相关产品推荐

