7
votes

I've got @ngrx/store and effects working fine, however, I just realized there will be a a lot of API calls (in effects), and if any of those returns a 401 error I should redirect the user to the login page. My problem is: I don't want to check that in every single effect, that would be a ton extra code for the same thing. Let's say for example I have a code like this:

Sample effect

@Effect() getMe$ = this.actions$
    .ofType(GET_ME)
    .map(action => action.payload)
    .switchMap(payload => this.userService.me()
        .map(res => ({ type: GET_ME_SUCCESS, payload: res }))
        .catch(() => Observable.of({ type: GET_ME_FAILURE }))
    );

userService.me()

me(): Observable<User> {
  return this.apiService.get(`/auth/me`);
}

apiService.get()

get(endpoint: string): Observable<any> {
  return this.http.get(`${this.base}${endpoint}`, this.options())
    .map(res => res.json());
}

This works perfectly fine, but I'm not sure how to handle the case when the API return 401. Where should I redirect the user globally in that case? Should I create an action for that case? Where should I dispatch that action then? Or am I doing it completely wrong?

Any help in the right direction would be appreciated!

1

1 Answers

13
votes

The errors that are emitted from Http will contain a status property (set to the HTTP status code) if they are errors that were received from the server.

If you include the error status in your HTTP-based service failure actions:

@Effect() getMe$ = this.actions$
    .ofType(GET_ME)
    .map(action => action.payload)
    .switchMap(payload => this.userService.me()
        .map(res => ({ type: GET_ME_SUCCESS, payload: res }))
        .catch(error => Observable.of({
            type: GET_ME_FAILURE,
            payload: { errorStatus: error.status }
        }))
    );

You could then write a general-purpose effect that looks at all actions and redirects if they contain a 401 error:

@Effect() errorStatus401$ = this.actions$
    .map(action => action.payload)
    .filter(payload => payload && payload.errorStatus === 401)
    .switchMap(payload => {
        this.router.navigate(['/login']);
        return Observable.empty();
    });

Or, if you use the @ngrx/router-store:

import { go } from '@ngrx/router-store';
...

@Effect() errorStatus401$ = this.actions$
    .map(action => action.payload)
    .filter(payload => payload && payload.errorStatus === 401)
    .map(payload => go(['/login']));

If there are additional actions you wish to perform before navigating, you can emit multiple actions using concat:

@Effect() errorStatus401$ = this.actions$
    .map(action => action.payload)
    .filter(payload => payload && payload.errorStatus === 401)
    .switchMap(payload => Observable.concat({ type: 'CLEAR_TOKEN' }, go(['/login'])));