Observable Error Handling

So I’ve been using NGRX, a redux library for Angular 2+, for managing my states for my personal angular projects. Its pretty great and allows you to easily scaffold your effects and reducers.

An observable stream ends off or dies when it encounters an error. In order to “re-continue” the stream, you’ll need to spawn a new observable to take its place. This can be done by catching the error and returning a new observable to take its place.

I encountered an odd issue when I tried to do this. The following shows my initial code.

@Effect()
identityLogout$: Observable<Action> = this.actions$.ofType(fromIdentityActions.LOGOUT_IDENTITY)
  .pipe(
    concatMap(() => this.identityService.checkAuthValidity()),
    concatMap((identityState: IdentityState | null): Observable<any> => {
      const bodyContent = this.apiService.constructJsonRequestBody('json-web-tokens', identityState.token);

      return this.apiService.request('DELETE', '/json-web-tokens/' + identityState.token, {body: bodyContent}, false);
    }),
    first(),
    map(() => {
      // Set local storage state to logout
      localStorage.setItem('identity-state', JSON.stringify(INITIAL_APP_STATE.identity));

      return new fromIdentityActions.LogoutIdentitySuccess();
    }),
    catchError(error => {
      // Do action that processes error
      return of(new fromIdentityActions.LogoutIdentityFail(error));
    })
  );

The above code shows an effect that logs the user out. This is done by telling the API endpoint to delete my JSON Web Token. Thereafter, the information of the identity state and json web token is removed from the local storage.

I thought the error was handled properly and the stream should continue. However, I was wrong. The error was handled properly only on the first call. The consecutive calls would result in the this.apiService.request not being called.

This causes a few issues:

  • New state cannot be propagated through the old observable
  • Error is caught; prevents problematic side effects on only first call
  • Calling the same method after error will not dispatch its effect

I was very confused by this initially. The root cause is that the new observable has overwritten everything that could have been done by its respective Effect.

To rectify this issue, I needed to limit the scope of the catchError to only the this.apiService.request call.

@Effect()
identityLogout$: Observable<Action> = this.actions$
  .pipe(
    ofType(fromIdentityActions.LOGOUT_IDENTITY),
    switchMap(() => this.identityService.checkAuthValidity()),
    switchMap((identityState: IdentityState | null): Observable<any> => {
      const bodyContent = this.apiService.constructJsonRequestBody('json-web-tokens', identityState.token);

      return this.apiService.request('DELETE', '/json-web-tokens/' + identityState.token, {body: bodyContent}, false)
        .pipe(
          first(),
          mergeMap(() => {
            // Set local storage state to logout
            return [
              new fromIdentityActions.SetIdentity((INITIAL_APP_STATE.identity)),
              new fromIdentityActions.LogoutIdentitySuccess()
            ];
          }),
          catchError(error => {
            // Do action that processes error
            return of(new fromIdentityActions.LogoutIdentityFail(error));
          })
        );
    }),
  );

Take note that the place where I pipe the catchError has moved.

Overall, observables and NGRX are great. But I think there’s insufficient details on handling errors. Perhaps this would help you. If you’re still experiencing issues with your Angular 2+ code, feel free to post your questions and I’ll try my best to help!

Author: Woo Huiren

Howdy, I'm a IT student who is currently serving my full time national service. I do lots of things related to IT but I have an exceptionally great love for the web and opensource, so expect me to blog a lot about it! :)

Leave a Reply