Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Angular2 + ngrx/store for handling failure HTTP requests

I want to have a simple code path for creating and dispatching HTTP actions. What I would like to do is something like:

this.http.request(...)
  .map((res: Response) => res.json())
  .catch((err: any) => err.json())
  .map((payload: any) => { type: 'SUCCESS', payload })
  .catch((payload: any) => { type: 'FAILURE', payload})
  .subscribe((action: Action) => this.store.dispatch(action));

That way both the success and failure responses are converted to JSON and then based upon the success/fail criteria assign the correct reduction type so that the store can be operated on properly. (think user login success and failure which returns a 200 or 401).

Is there a cleaner or better way of handling this? Current the 2nd .catch doesn't play well since it is not returning an observable.

Suggestions or other solutions welcome?

like image 786
nathasm Avatar asked Feb 13 '16 19:02

nathasm


2 Answers

From the example-app from ngrx, for this case is recommended to use @Effects(check the docs folder), and IMO, is a more clear way, check the service:

@Injectable()
export class AuthService {
    private headers: Headers;
    private API_ENDPOINT: string = "/api/user/";

    public constructor(
        private http: Http,
        private localStorageService: LocalStorageService
        ) {
        this.headers = new Headers({ 'Accept': 'application/json' });
    }

    public login(email: string, password: string): Observable<AuthUser> {
        return this.http
        .post(this.API_ENDPOINT + 'login', { 'email': email, 'password': password }, this.headers)
        .map(res => res.json().data as AuthUser)
        .catch(this.handleError);
    }

    private handleError(error: Response | any) {
        let body = error.json();
        // .. may be other body transformations here
        console.error(body);
        return Observable.throw(body);
    }
}

And check the Effect:

@Injectable()
export class AuthEffects {

    constructor(
        private actions$: Actions,
        private authService: AuthService,
        private localStorageService: LocalStorageService
    ) { }

    @Effect() logIn$: Observable<Action> = this.actions$
        .ofType(auth.ActionTypes.LOGIN)
        .map((action: Action) => action.payload as LoginCredentials)
        .switchMap((credentials: LoginCredentials) => this.authService.login(credentials.email, credentials.password))
        .do((user: AuthUser) => this.localStorageService.setUser(user))
        .map((user: AuthUser) => new auth.LoginSuccessAction(user))
        .catch((error) => of(new auth.FlashErrors(error)));

}

Off course, you need to setup the Effects on appModule:

@NgModule({
imports: [
    StoreModule.provideStore(reducer),
    EffectsModule.run(AuthEffects),
    RouterStoreModule.connectRouter(), // optional but recommended :D
],
declarations: [...],
providers: [AuthService, LocalStorageService, ...]
})
export class AuthModule {}

Read more about ngrx/effects on the docs folder from the repo.

like image 169
J0H4N_AC Avatar answered Oct 15 '22 11:10

J0H4N_AC


In one of my services I do it like this:

get(url, actionType) {
  this._http.get(BASE_URL + url)
    .map(response => response.json())
    .map(payload => ({ type: actionType, payload }))
    .subscribe(action => this.store.dispatch(action), error => this._apiErrorHandler(error));
}

private _apiErrorHandler(response) {
  let payload = response.json().error;
  this.store.dispatch({ type: 'API_ERROR', payload });
}
like image 33
Sasxa Avatar answered Oct 15 '22 13:10

Sasxa