确保在发出下一个值之前发生订阅的最佳方法

问题描述

我正在使用 OAuth 2.0 在我的 Angular 应用程序中为我的用户检索令牌。我使用流行的库 angular-oauth2-oidc 来处理 OAuth 流程。因此,在我的 AppComponent 中,我从 angular-oauth2-oidc 设置了 oAuthService,然后通过调用 initCodeFlow() 在我的登录组件中启动 oAuth 流程。用户重定向以输入他的凭据,然后他被重定向到我,我在 AppComponent 中收到令牌并将其作为下一个令牌放入我的 DataService TokenSubject。

但现在我想在 MainPageComponent 的标头中使用该令牌进行 API 调用。所以我在我的 MainPage 中订阅了 TokenSubject,当我收到令牌时,订阅块中的代码就会被执行。

但是,如果令牌 get 比主页建立得更快发送给我怎么办?然后,当 AppComponent 发出下一个 Token 值时,我还没有订阅主页中的 TokenSubject。我不确定,但我的主页有时没有打开,我想这可能是原因。

在我发出 TokenSubject 之前,确保主页订阅 TokenSubject 的最佳方法是什么?

我的AppComponent

import { OAuthService } from 'angular-oauth2-oidc';
...

export class AppComponent implements OnInit {
    token: Token = {
      access_token: null,refresh_token: null
};

constructor(private oauthService: OAuthService,private adapterService: AdapterService,private dataService: DataService,private logger: NGXLogger) {
}

ngOnInit() {
        this.configureOAuthService();
        this.subscribetoOAuthEvents();
    }

configureOAuthService() {
        const authConfig: AuthConfig = {
            issuer: environment.oauthLoginUrl,redirectUri: environment.oauthRedirectUrl,clientId: environment.apiKey,scope: 'openid',responseType: 'code',showDebug@R_53_4045@ion: true,disablePKCE: false,oidc: true,disableAtHashCheck: false,};

        this.oauthService.configure(authConfig);
        this.oauthService.tokenValidationHandler = new JwksValidationHandler();
        this.oauthService.loaddiscoveryDocumentAndTryLogin();
}

subscribetoOAuthEvents() {
        this.oauthService.events.subscribe(e => {
            if (e.type === 'token_received' || e.type === 'token_refreshed' ) {
                this.storetokenInDataService();

                const self = this;
                // this will wait for 2 hours minus 30 sec before it refreshes the access token;
                setTimeout(() => { self.refreshToken(self); },7_170_000);
            } else if (e.type === 'token_expires') {
                this.logger.log('token_expires,going to refresh');
                this.refreshToken(this);
            } else if (e instanceof OAuthErrorEvent) {
                this.logger.error('OAuth error:',e);
            } else {
                this.logger.log('OAuthEvent:',e);
            }
        });
    }

    storetokenInDataService() {
        this.token.access_token = this.oauthService.getAccesstoken();
        this.token.refresh_token = this.oauthService.getRefreshToken();
        this.dataService.nextToken(this.token);
    }

在我的主页中,我订阅了 sharedToken,但我不确定这是否总是在 Token 发出之前发生:

ngOnInit(): void {
  console.log('going to get the token from dataService');
  this.dataService.sharedToken
    .pipe(
      takeuntil(this.destroy$),tap(token => {
        this.token = token;
        console.log('got Token');
      }),switchMap(token => {
        this.headers = this.createHeaders(token.access_token);

        return this.adapterService.getUserInfo(this.headers);
      }))
    .subscribe(UserInfo => {
      this.logger.log('received userInfo : ',userInfo);
      ... 
  });
 }
}

在我的 DataService 中,它看起来像这样:

  private token = new Subject<Token>();
  sharedPToken = this.token ;

  nextToken(token: Token) {
    this.token.next(token);
  }

更新:问题解决了,我的最终主要组件如下所示:

ngOnInit(): void {
  this.dataService.sharedTokenReplaySubject
    .pipe(
      takeLast(1),userInfo);
      ... 
  });
 }
}

解决方法

简而言之:您无法通过 Subject 确保它。

您可以改用 RxJS ReplaySubject。它是您使用的更通用的 Subject 的变体。然而,它可以“保持/缓冲”特定数量的先前发射,并在订阅后立即发送给未来的订阅者。在您的情况下,您可以使用缓冲区 1。它会保存并发出推送给它的最后一个推送值。

private token = new ReplaySubject<Token>(1);  // <-- buffer 1
sharedToken = this.token.asObservable();

nextToken(token: Token) {
  this.token.next(token);
}

这样您就不必担心在订阅 token 之前或之后是否将值推送到 sharedToken。即使值在订阅前推送到 this.dataService.sharedToken,订阅 token 也会获得令牌。