Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Angular 2, catching 401 in @ngrx/effects

Tags:

angular

ngrx

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!

like image 754
Andrew Avatar asked Nov 05 '16 19:11

Andrew


People also ask

How do you call an effect in NgRx?

You need to run this command npm install @ngrx/effects — save to install required dependencies. You have to define the actual services to make the API calls. Finally, you need to register all the effects with the EffectsModules and import this module into your main module or feature module.

When should you not use NgRx?

When should you not use NgRx? Never use NgRx if your application is a small one with just a couple of domains or if you want to deliver something quickly. It comes with a lot of boilerplate code, so in some scenarios it will make your coding more difficult.

Is NgRx a cache?

This is trivial within an application that is using the global NgRx Store, as the global Store is just a cache object.

What is ofType NgRx?

'ofType' filters an Observable of Actions into an observable of the actions whose type strings are passed to it.


1 Answers

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'])));
like image 131
cartant Avatar answered Oct 16 '22 13:10

cartant