Handling HTTP Error Messages `Globally` using Interceptors and Toasters [Angular]
Tags: angular, interceptor, servererrors, toaster
Published: 2020-10-25 21:42:03

Handle all HTTP error messages in your whole **Angular** app using **Interceptors** and **NGX-Toaster**.

##What are Interceptors?
According to Angular official docs, Interceptors are .ts files that [Intercept](https://angular.io/api/common/http/HttpInterceptor#description) and handle HTTP Request or HTTP Response.

In other words, every single HTTP request or response will be passed through an interceptor, that way we can listen to HTTP failure response in order to handle the error in the UI based on the [status code](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status) of the response.

##Creating an Interceptor.
An interceptor is a service that implements [HttpInterceptor](https://angular.io/api/common/http/HttpInterceptor#description) interface, So we will need the `@Injectable( )` decorator and `intercept( )` method as the following:

```typescript
import { Injectable } from "@angular/core";
import {
  HttpInterceptor,
  HttpHandler,
  HttpRequest,
  HttpEvent,
} from "@angular/common/http";

@Injectable()
export class ServerErrorsInterceptor implements HttpInterceptor {
  // intercept function
  public intercept(
    req: HttpRequest<any>,
    next: HttpHandler
  ): Observable<HttpEvent<any>> {
      return new Observable()
    }
}

```
The intercept method takes 2 arguments, `req`, and `next`

**req:** The outgoing request object to handle.

**next:** The next interceptor in the chain, or the backend if no interceptors remain in the chain.

and it returns an Observable.

And in the **app.module.ts**

Import **HTTP_INTERCEPTORS** from **@angular/common/http** and add an object to the providers' array like the following:

app.module.ts
```typescript
import { HTTP_INTERCEPTORS } from "@angular/common/http";
import { ServerErrorsInterceptor } from "./serverErrorsInterceptor";

@NgModule({
  //..
  providers: [{
    provide: HTTP_INTERCEPTORS,
    useClass: ServerErrorsInterceptor,
    multi:true
  }],
  //..
})
```
##Subscribing to HTTP requests.
Now inside the returned Observable let's create the logic to handle the errors

ServerErrorsInterceptor.ts
```typescript
export class ServerErrorsInterceptor implements HttpInterceptor {

  // intercept function
  public intercept(
    req: HttpRequest<any>,
    next: HttpHandler
  ): Observable<HttpEvent<any>> {

    // returning an observable to complete the request cycle
    return new Observable((observer) => {
      next.handle(req).subscribe(
        (res: HttpResponse<any>) => {
          if (res instanceof HttpResponse) {
            observer.next(res);
          }
        },
        (err: HttpErrorResponse) => {
          console.error(err);
        }
      );
    });
  }
}
```
We will pass the `req` to `next.handle( )` method and subscribe to it.

In the success case, we will just call the `next(res)` to complete the cycle of the HTTP response.

And for the failure case, We will handle the HTTP error message, Right now we will just `console.error(err)`.

##Creating HandleErrorService.
Instead of consoling the whole error directly inside the Interceptor, let's create a service to do so as the following:

HandleErrorService.ts
```typescript
import { Injectable } from "@angular/core";
import { HttpErrorResponse } from "@angular/common/http";

@Injectable({
  providedIn: "root",
})

export class HandleErrorService {
  constructor() {}

  // Handling HTTP Errors using Toaster
  public handleError(err: HttpErrorResponse) {
    let errorMessage: string;
    if (err.error instanceof ErrorEvent) {
      // A client-side or network error occurred. Handle it accordingly.
      errorMessage = `An error occurred: ${err.error.message}`;
    } else {
      // The backend returned an unsuccessful response code.
      errorMessage = "Something went wrong!";
    }
    console.error(errorMessage);
  }

}
```
In the handleError method, we take an **HttpErrorResponse** as an argument and separate the **client-side** and the **backend** errors.

For now, we will just console a generic message for the backend error "Something Went Wrong".

#Installing [ngx-toastr](https://www.npmjs.com/package/ngx-toastr).

```
npm i ngx-toastr
```

Now in **HandleErrorService.ts** import **ToasterService** from 'ngx-toastr' and substitute **console.error** with **toaster.error**

HandleErrorService.ts
```typescript
import { Injectable } from "@angular/core";
import { ToastrService } from "ngx-toastr";
import { HttpErrorResponse } from "@angular/common/http";

@Injectable({
  providedIn: "root",
})
export class HandleErrorService {
  constructor(private toastrs: ToastrService) {}

  // Handling HTTP Errors using Toaster
  public handleError(err: HttpErrorResponse) {
    let errorMessage: string;
    if (err.error instanceof ErrorEvent) {
      // A client-side or network error occurred. Handle it accordingly.
      errorMessage = `An error occurred: ${err.error.message}`;
    } else {
      // The backend returned an unsuccessful response code.
      errorMessage = "Something went wrong!";
    }
    this.toasters.error(errorMessage);
  }
}

```
And in app.module.ts

Import **ToastrModule** and **BrowserAnimationsModule** and you can change the options of the toaster like the following:

app.module.ts
```typescript
import { BrowserAnimationsModule } from "@angular/platform-browser/animations";
import { ToastrModule } from "ngx-toastr";

@NgModule({
  //..
  imports: [
    //..
    BrowserAnimationsModule,
    ToastrModule.forRoot({
      timeOut: 5000,
      positionClass: "toast-bottom-left",
    }),
    //..
  ],
  //..
})
```

For my code, I made the toaster to appear from the left bottom and it will take 5sec until it disappears. [(Learn More About Toasters)](https://ngx-toastr.vercel.app/)

Now back to our **Interceptor** Import the created **HandleErrorService** and substitute the console.error with the **HandleErrorService** method:

ServerErrorsInterceptor.ts
```typescript
import { Injectable } from "@angular/core";
import {
  HttpInterceptor,
  HttpHandler,
  HttpRequest,
  HttpEvent,
} from "@angular/common/http";

import { HandleErrorService } from "./services/handleError.service";

export class ServerErrorsInterceptor implements HttpInterceptor {
  constructor(
    private error: HandleErrorService,
  ) {}

  // intercept function
  public intercept(
    req: HttpRequest<any>,
    next: HttpHandler
  ): Observable<HttpEvent<any>> {

    // returning an observable to complete the request cycle
    return new Observable((observer) => {
      next.handle(req).subscribe(
        (res: HttpResponse<any>) => {
          if (res instanceof HttpResponse) {
            observer.next(res);
          }
        },
        (err: HttpErrorResponse) => {
          this.error.handleError(err);
        }
      );
    });
  }
}
```
Now in every single HTTP failure, the **Interceptor** will handle it and display the messages via **Toasters**.

So far and so good!

We can take the **handleError** method to the next level and display the backend error message accordingly to the status code instead of the generic "Something Went Wrong" message.
```typescript
  // Handling HTTP Errors using Toaster
  public handleError(err: HttpErrorResponse) {
    let errorMessage: string;
    if (err.error instanceof ErrorEvent) {
      // A client-side or network error occurred. Handle it accordingly.
      errorMessage = `An error occurred: ${err.error.message}`;
    } else {
      // The backend returned an unsuccessful response code.
      switch (err.status) {
        case 400:
          errorMessage = "Bad Request.";
          break;
        case 401:
          errorMessage = "You need to log in to do this action.";
          break;
        case 403:
          errorMessage = "You don't have permission to access the requested resource.";
          break;
        case 404:
          errorMessage = "The requested resource does not exist.";
          break;
        case 412:
          errorMessage = "Precondition Failed.";
          break;
        case 500:
          errorMessage = "Internal Server Error.";
          break;
        case 503:
          errorMessage = "The requested service is not available.";
          break;
        case 422:
          errorMessage = "Validation Error!";
          break;
        default:
          errorMessage = "Something went wrong!";
      }
    }
    if (errorMessage) {
      this.toasters.Error(errorMessage);
    }
  }
```

<a href="https://www.linkedin.com/in/youssefzidan/" target="_blank">
    <img src="https://img.shields.io/badge/LinkedIn-%230077B5.svg?&style=flat-square&logo=linkedin&logoColor=white" alt="LinkedIn">