In the previous article, we discussed in detail on how to make HTTP requests to APIs from an Angular Application and how HttpInterceptors help us in tweaking and fiddling the requests or responses as they move out of the application scope and before they reach their calling components. We have also looked at how we can create our own HttpInterceptor by implementing the HttpInterceptor interface and for our requirement we’ve used it to add a default Request Header which must be passed on for all requests which are headed towards Posts API.
Another possible use case of HttpInterceptors can be catching exceptions which might occur during an API request and handle these scenarios so that the calling components or services may not need to handle them explicitly, leading to a sort of safe-path for all HTTP requests.
To implement this, we create another class ErrorInterceptor which implements HttpInterceptor and within the intercept() method, instead of working on the HttpRequest object like how we done before, we shall now work with the aftermath of a HTTP call; once the next.handle() returns to this interceptor.
import {
HttpInterceptor,
HttpRequest,
HttpHandler,
HttpEvent,
HttpErrorResponse
} from '@angular/common/http';
import { Observable, throwError } from 'rxjs';
import { catchError } from 'rxjs/operators';
export class ErrorInterceptor implements HttpInterceptor {
intercept(req: HttpRequest<any>, next: HttpHandler)
: Observable<HttpEvent<any>> {
var res = next.handle(req);
res.pipe(
catchError((error: HttpErrorResponse) => {
// process the obtained error
// for logging or monitoring
console.log("Interceptor Log: " + error.message);
// create new Observable stream
// which the clients
// can subscribe and
// catch the Erroneous response
return throwError(error);
}));
}
}
Observe that we’ve added a pipe() to the HttpResponse Observable, which the next.handle(req) returns. This res object contains the result of the HTTP request which has been sent to the remote API and a response is awaited.
We have already seen how pipe() works: using a pipe() we pull out the Response stream and then catch in case the response stream has thrown an exception in its course of HTTP request. How do we catch it? We make use of catchError – an RxJs operator which tracks the stream and catches any runtime errors which occur.
Within the catchError() we add a callback, to which the error of the type HttpErrorResponse is passed. Within this method, we can handle the error we have now caught and proccess it to any logging engine (like all other applications do) or do something else.
Finally, we pass on the error using throwError() method, which creates a new Stream with the error that we’re now aware of. At clients, we can catch this error during the subscription and handle it according to the context.
this.postsUpdatedSubs = this.postsService.getPosts();
this.postsUpdatedSubs.subscribe((posts) => {
this.posts = [...posts];
}, (error) => {
// handle error
console.log("Error in PostListComponent: " + error.message);
});
We register this Interceptor at the AppModule level, so that it can be available for access across all the sub modules in the application.
@NgModule({
declarations: [
AppComponent,
NavbarComponent
],
imports: [
BrowserModule,
PostsModule,
AuthModule,
RouterModule.forRoot(routes)
],
providers: [{
provide: HTTP_INTERCEPTORS,
multi: true,
useClass: ErrorInterceptor
}],
bootstrap: [AppComponent]
})
export class AppModule { }
Observe that we have defined our ErrorInterceptor in the same way as we define any other HTTP Interceptor and attached it to the HTTP request pipeline. Once we run the application, every HTTP request made by the application would pass through the ErrorInterceptor we just registered along with all other Interceptors created as well as the default ones built into angular system.
When an error occurs, the ErrorInterceptor receives the error through the catchError() operator, where it gets access to the error object casted to HttpErrorResponse type. Its up to us how we can handle it: we can log it or do some analysis out of it or any other app specific operation.
Finally, we rethrow the error as a new Observable stream which the subscribed components receive and handle in their own way.
For example, back in our OpenSocialApp let’s run the application without having the API available, and see if the error is handled by the Interceptor as we expected.
As we can see in the logs printed on the Developer Console, the response passes through the ErrorInterceptor which first catches and prints the Error Log and later on is handled at the PostListComponent which also catches and reinitializes its posts model so that the view is not impacted of any issues.