I'm using Angular and rxjs 5.x.x . For each http request I must log the response in a centralized place -either if success or fail.
The problem which i'm facing is in a cituation where a catch
clause is being called.
For example :
callRemote() {
return this.http.get("http://aaa.dom")
.catch(f=>Rx.Observable.of(f))
.do(f => console.log("logging response both bad and ok..."))
}
This is an http
request which fails. But I must invoke the do
operator becuase I need to log it.
However if a .catch
is called , the .do
won't be called. That's why I did this :
.catch(f=>Rx.Observable.of(f))
So I'm basically catching the error and wrap it as a new observable and continue to the .do
function.
So far so good.
The problem now is that ,eventually , when subscribing , I want to know if request failed or OK.
So basically this is how I subscribe :
this._http.callRemote().subscribe(
(x => console.log('OK')),
(x => console.log('ERROR')),
(() => console.log('COMPLETE'))
);
Problem is that everything is redirected to the sucess
callback function. Even failures. I do understand why it happend - it's becuase I wrapped catch
fails as new Observables.
Question
Is it possible that wrapped errors will go to the error
callback , and not to the success
callback ?
As already mentioned in the comments, the catch shouldn't swallow the error in the first place, thats a nogo for sure. By doing that you are simply blocking the error signal in the rxjs chain.
Eventually you could do your logging in the catch and then re throw, but thats kinda overkill for only log side effect.
The simplest option would be to use the error callback arguent in the do
operator.
According to this tutorial page, the do operator can take 3 arguments (callback fns), what basically matches the subscribe signature:
1s argument: callback on next
2nd argument: callback on error
3rd argument: callback on complete
So you could refactor into the following:
callRemote() {
return this.http.get("http://aaa.dom")
.do(
response => console.log("logging response both bad and ok..."),
error => console.log("Something exploded, call 911");
}
So basically you could attach that operation to every single HttpClient
call in your base code. Pretty neat, no?
Wait wait! This might look neat on a first glance, but it will backfire at the very same moment that you want to:
Why?
You are going to basically monkey-patch every single possible back end call with that do() operation. And if making 1 change to that logic means changing code in more than 3 places, there something smelly going on.
A better approach
With the introduction of the HttpClient, another API was added: the HttpInterceptor API.
Basically, you could intercept all your outgoing requests in a single point.
How? As it follows:
First step, create an injectable service that you can use to capsulate the logging logic;
import { Injectable } from '@angular/core';
import {
HttpRequest,
HttpHandler,
HttpEvent,
HttpInterceptor
} from '@angular/common/http';
import { AuthService } from './auth/auth.service';
import { Observable } from 'rxjs/Observable';
import { tap } from 'rxjs/operators'; // fancy pipe-able operators
@Injectable()
export class LoggingInterceptor implements HttpInterceptor {
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
return next.handle(request).pipe(
tap(
response => console.log("Oh boy we got an answer"),
error => console.log("Something might be burning back there")
));
}
}
Second step, make angular aware of the existence of the LoggingInterceptor
by providing it through a token:
import { HTTP_INTERCEPTORS } from '@angular/common/http';
import { NgModule, ClassProvider } from '@angular/core';
import { LoggingInterceptor } from '../some-place';
const LOGGING_INTERCEPTOR_PROVIDER: ClassProvider = {
provide: HTTP_INTERCEPTORS ,
useClass: LoggingInterceptor,
multi: true
};
@NgModule({
...
providers: [
LOGGING_INTERCEPTOR_PROVIDER
]
...
})
export class AppModule {}
And thats it! Now you can log all your outgoing requests and do some other cool stuff with those logs if necessary, in a truly centralized way.