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