In this tutorial, you will be introduced to HTTP errors in JavaScript, and how to use the HttpClient
with RxJS error handling operators and finally how to use the HttpInterceptor
This is a deep dive into HTTP error handling in Angular 7, you might want to check out the introductory post on error handling here.
To be able to follow through in this tutorial's demonstration you should have:
1// run the command in a terminal 2 ng version
Confirm that you are using version 7, and update to 7 if you are not. Other things that will be nice-to-haves are:
These are errors that we call server side errors because they mainly come from the outside the Angular application and an HttpErrorResponse
is always returned anytime they occur. It has properties like:
Angular has a global error handling class called errorHandler that provides a hook for centralized exception handling inside your application. It basically intercepts all the errors that happen in your application, and logs all of them to the console, and stops the app from crashing. The syntax looks like this:
1class MyErrorHandler implements ErrorHandler { 2 handleError(error) { 3 // do something with the exception 4 } 5 } 6 @NgModule({ 7 providers: [{provide: ErrorHandler, useClass: MyErrorHandler}] 8 }) 9 class MyModule {}
This is a great way to handle errors in Angular, particularly the insider errors.
If you followed from the introductory post here, you will see how the Angular errorHandler class was the ultimate solution to centralizing the try/catch concept of errors in our application. However, when we want to focus on server side errors, we discover that the errorHandler class cannot work directly with HTTP requests in our application. Good news is Angular provides a kind of interface where you can use the concept of the errorHandler class to deal directly with HTTP requests.
The [HttpClient](https://angular.io/api/common/http/HttpClient)
in @angular/common/``[http](https://angular.io/api/common/http)
offers a simplified client HTTP API for Angular applications that rests on the XMLHttpRequest
interface exposed by browsers. Additional benefits of [HttpClient](https://angular.io/api/common/http/HttpClient)
include testability features, typed request and response objects, request and response interception, Observable
APIs, and streamlined error handling. So using this client with some RxJS operators we can get a kind of try/catch way of error handling but this time directly communicating with the HTTP requests through an Angular application in a service. You will understand it better in action.
This is a demo jsonplaceholder application where parts of the available data on the jsonplaceholder is displayed on the user interface, a perfect service to demonstrate server related concepts. If you have all the prerequisites stated at the beginning of the post ready, you can download the project from GitHub here. Unzip and open the project in VS Code and use the terminal to initialize the node modules:
npm install
Now that your application is up and running, you have to first and foremost ensure that the module required for Angular applications to use any server service is active in your application. Navigate to your app.module.ts
file and confirm that there is an import statement like this:
import { HttpClientModule } from '@angular/common/http';
Your application has four components: posts, sidebar, details and users. It also has a service called data service where all the HTTP requests are made. Your data.service.ts
file should look like this:
1// src/app/services/data.service.ts 2 import { Injectable } from '@angular/core'; 3 import { HttpClient } from '@angular/common/http'; 4 @Injectable({ 5 providedIn: 'root' 6 }) 7 export class DataService { 8 constructor(private http: HttpClient) { } 9 getUsers() { 10 return this.http.get('https://jsonplaceholder.typicode.com/users') 11 } 12 getUser(userId) { 13 return this.http.get('https://jsonplaceholder.typicode.com/users/'+userId) 14 } 15 getPosts() { 16 return this.http.get('https://jsonplaceholder.typicode.com/posts') 17 } 18 }
Three requests are being made to the server, now if you pick one of these requests, say getUsers()
and you want to add error handling with the HttpClient then you will:
If you follow these, your data.service.ts
file will look like this:
1// src/app/services/data.service.ts 2 import { Injectable } from '@angular/core'; 3 import { HttpClient, HttpErrorResponse } from '@angular/common/http'; 4 import { catchError } from 'rxjs/operators' 5 import { throwError } from 'rxjs'; 6 @Injectable({ 7 providedIn: 'root' 8 }) 9 export class DataService { 10 constructor(private http: HttpClient) { } 11 getUsers() { 12 return this.http.get('https://jsonplaceholder.typicode.com/usssers') 13 .pipe( 14 catchError(this.handleError) 15 ); 16 } 17 getUser(userId) { 18 return this.http.get('https://jsonplaceholder.typicode.com/users/'+userId) 19 } 20 getPosts() { 21 return this.http.get('https://jsonplaceholder.typicode.com/posts') 22 } 23 handleError(error: HttpErrorResponse){ 24 console.log("lalalalalalalala"); 25 return throwError(error); 26 } 27 }
You will see that the get request was deliberately tampered with to ensure an error occurs. When you run the application, you will see the error logged with the log message we want.
Sometimes when you send a request to a well known slow server, you know it might take a while to get response or take a few tries to actually get response from the server, you can resend the request a number of times before throwing the error. This can be achieved with the retry method in RxJS, so you import the retry operator then you can use it inside the pipe like it is used below:
1// src/app/services/data.service.ts 2 import { Injectable } from '@angular/core'; 3 import { HttpClient, HttpErrorResponse } from '@angular/common/http'; 4 import { catchError, retry } from 'rxjs/operators' 5 import { throwError } from 'rxjs'; 6 @Injectable({ 7 providedIn: 'root' 8 }) 9 export class DataService { 10 constructor(private http: HttpClient) { } 11 getUsers() { 12 return this.http.get('https://jsonplaceholder.typicode.com/usssers') 13 .pipe( 14 retry(2), 15 catchError(this.handleError) 16 ); 17 } 18 getUser(userId) { 19 return this.http.get('https://jsonplaceholder.typicode.com/users/'+userId) 20 } 21 getPosts() { 22 return this.http.get('https://jsonplaceholder.typicode.com/posts') 23 } 24 handleError(error: HttpErrorResponse){ 25 console.log("lalalalalalalala"); 26 return throwError(error); 27 } 28 }
If you run the application, the console should look like this:
You see it first tries to get the response, then retries it twice just as we specified before throwing the error log message.
It is also very important that your retry comes before the catchError so that the error message is not logged after every trial.
This solution works perfectly so long as your application has one service and probably one get request, but when your application is big and has many services or a lot more requests per service it becomes an inefficient solution. This is because you have to always copy the handle error function across services and repeat code even within a service. Imagine the memory cost of debugging and maintaining the codebase.
Just like the name says, Angular provides an interface called the HttpInterceptor
that can intercept [HttpRequest](https://angular.io/api/common/http/HttpRequest)
and [HttpResponse](https://angular.io/api/common/http/HttpResponse)
and creates a platform to handle them. This means we get direct access to our server requests, what better place to deal with server errors than here!
The syntax looks like this:
1interface HttpInterceptor { 2 intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> 3 }
To use the HttpInterceptor
, create a new service where you want your interceptor logic to go in with the Angular CLI:
ng generate service services/interceptor
Now you have generated an interceptor service, navigate to your app.module.ts
file to register it accordingly, like this:
1// src/app/app.module.ts 2 import { BrowserModule } from '@angular/platform-browser'; 3 import { NgModule } from '@angular/core'; 4 import { AppRoutingModule } from './app-routing.module'; 5 import { AppComponent } from './app.component'; 6 import { UsersComponent } from './components/users/users.component'; 7 import { DetailsComponent } from './components/details/details.component'; 8 import { PostsComponent } from './components/posts/posts.component'; 9 import { SidebarComponent } from './components/sidebar/sidebar.component'; 10 import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http'; 11 import { BrowserAnimationsModule } from '@angular/platform-browser/animations' 12 import { InterceptorService } from './services/interceptor.service'; 13 @NgModule({ 14 declarations: [ 15 AppComponent, 16 SidebarComponent, 17 PostsComponent, 18 DetailsComponent, 19 UsersComponent 20 ], 21 imports: [ 22 BrowserModule, 23 AppRoutingModule, 24 HttpClientModule, 25 BrowserAnimationsModule 26 ], 27 providers: [ 28 { 29 provide: HTTP_INTERCEPTORS, 30 useClass: InterceptorService, 31 multi: true 32 } 33 ], 34 bootstrap: [AppComponent] 35 }) 36 export class AppModule { }
Next step is to get rid of all the error handling logic in the data.service.ts
file, the file should look like this when you are done:
1// src/app/services/data.service.ts 2 import { Injectable } from '@angular/core'; 3 import { HttpClient } from '@angular/common/http'; 4 @Injectable({ 5 providedIn: 'root' 6 }) 7 export class DataService { 8 constructor(private http: HttpClient) { } 9 getUsers() { 10 return this.http.get('https://jsonplaceholder.typicode.com/usersss') 11 } 12 getUser(userId) { 13 return this.http.get('https://jsonplaceholder.typicode.com/users/'+userId) 14 } 15 getPosts() { 16 return this.http.get('https://jsonplaceholder.typicode.com/posts') 17 } 18 }
Copy the code below into the interceptor.service.ts
file:
1// src/app/services/interceptor.service.ts 2 import { Injectable } from '@angular/core'; 3 import { 4 HttpInterceptor, HttpRequest, 5 HttpHandler, HttpEvent, HttpErrorResponse 6 } from '@angular/common/http'; 7 import { Observable, throwError } from 'rxjs'; 8 import { catchError } from 'rxjs/operators'; 9 @Injectable({ 10 providedIn: 'root' 11 }) 12 export class InterceptorService implements HttpInterceptor{ 13 constructor() { } 14 handleError(error: HttpErrorResponse){ 15 console.log("lalalalalalalala"); 16 return throwError(error); 17 } 18 intercept(req: HttpRequest<any>, next: HttpHandler): 19 Observable<HttpEvent<any>>{ 20 return next.handle(req) 21 .pipe( 22 catchError(this.handleError) 23 ) 24 }; 25 }
If you run the application, you can see that it logs our error message and throws the error just as we expect. This is the best method of handling server errors in your Angular project. You can test all the three requests at once for errors by tampering with them. When you do, you will find out that the interceptor catches all of them and logs our message for each, it is really amazing to watch.
You have been introduced to various ways to handle server side errors in your Angular applications. You have also seen when to use and when not to use the HttpClient
with the RxJS operators and how the best way is using interceptors. In the next tutorial in this series, you will be introduced to error tracking. The complete code for this tutorial is on GitHub and can be found here. Happy coding!