Search code examples
angularrxjsngrx

Stop initialization of app until ngrx's action has been called


I try to synchronize initialization of app and initialization of ngrx store. It's important because app uses the refresh token mechanism to auto-login. Race produces many problems like cannot read property of undefined.

Method below is used in APP_INITIALIZER token.

// import { ActionsSubject, ScannedActionsSubject } from '@ngrx/store';
    
public async load(): Promise<any> {

    return this.refreshToken().pipe(
      tap(res => {
        this.subject.next(authActions.refreshTokenOnInitSuccess({token: res.token}));
      }),
      catchError((error: HttpErrorResponse) => {
      // SOME CODE
      }),
     takeUntil(this.actions$.pipe(ofType(authActions.initSuccess))) //  HERE, init not stop, work like without this line
    ).toPromise().catch(error => {
      console.error('Error when refreshing JWT access token on startup', error);
    });
  }

  private refreshToken(): Observable<JwtTokenResponse> {
    return this.httpClient.post<JwtTokenResponse>(`${AUTH_URL}/refresh`, {});
  }

I tried to use exhaustMap instead of takeUntil but initialization never ended. I know it can be fixed by Promise with resolve like:

 return new Promise((resolve, reject) => {
      this.refreshToken().pipe(
        tap(res => {
          this.subject.next(authActions.refreshTokenOnInitSuccess({ token: res.token }));
        }),
        catchError((error: HttpErrorResponse) => {
          // SOME CODE
          reject(error);
          return throwError(error);
        }),
      ).subscribe();
      this.actions$.pipe(ofType(authActions.initSuccess)).subscribe(resolve);
    }).catch(error => {
      console.error('Error when refreshing JWT access token on startup', error);
    }); 

but I try to find rxjs way to pause initialization of app until store set state.


Solution

  • You have to subscribe to your init success action before you trigger this action. You can use forkJoin to combine your action and your refresh/trigger stream. (combineLatest or zip should also work)

    public async load(): Promise<any> {
      const refresh = this.refreshToken().pipe(
        tap(res => {
          this.subject.next(authActions.refreshTokenOnInitSuccess({token: res.token}));
        }),
        catchError((error: HttpErrorResponse) => {
          // SOME CODE
        }),
      );
      return forkJoin(
        // add your action stream first, use take(1) to complete the stream
        this.actions$.pipe(ofType(authActions.initSuccess), take(1)), 
        refresh    
      ).toPromise().catch(error => {
        console.error('Error when refreshing JWT access token on startup', error);
      });
    }