Xalion
Xalion

Reputation: 657

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.

Upvotes: 0

Views: 888

Answers (2)

frido
frido

Reputation: 14129

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);
  });
}

Upvotes: 1

mike
mike

Reputation: 1863

When I run into such things I wrap my observables/subscription in a Promise:

const result = await new Promise<string>(resolve => {
    this.service.subscribe(res => {
        resolve(res);
    });
});

Should work for your case if implemented correctly.

Upvotes: 0

Related Questions