Handling HTTP Error Messages `Globally` using Interceptors and Toasters [Angular]

youssefzidan

Youssef Zidan

Posted on October 25, 2020

Handling HTTP Error Messages `Globally` using Interceptors and Toasters [Angular]

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 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 of the response.

Creating an Interceptor.

An interceptor is a service that implements HttpInterceptor interface, So we will need the @Injectable( ) decorator and intercept( ) method as the following:



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()
    }
}



Enter fullscreen mode Exit fullscreen mode

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



import { HTTP_INTERCEPTORS } from "@angular/common/http";
import { ServerErrorsInterceptor } from "./serverErrorsInterceptor";

@NgModule({
  //..
  providers: [{
    provide: HTTP_INTERCEPTORS,
    useClass: ServerErrorsInterceptor,
    multi:true
  }],
  //..
})


Enter fullscreen mode Exit fullscreen mode

Subscribing to HTTP requests.

Now inside the returned Observable let's create the logic to handle the errors

ServerErrorsInterceptor.ts



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


Enter fullscreen mode Exit fullscreen mode

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



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

}


Enter fullscreen mode Exit fullscreen mode

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.



npm i ngx-toastr


Enter fullscreen mode Exit fullscreen mode

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

HandleErrorService.ts



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



Enter fullscreen mode Exit fullscreen mode

And in app.module.ts

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

app.module.ts



import { BrowserAnimationsModule } from "@angular/platform-browser/animations";
import { ToastrModule } from "ngx-toastr";

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


Enter fullscreen mode Exit fullscreen mode

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)

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

ServerErrorsInterceptor.ts



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


Enter fullscreen mode Exit fullscreen mode

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.



  // 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);
    }
  }


Enter fullscreen mode Exit fullscreen mode


LinkedIn

💖 💪 🙅 🚩
youssefzidan
Youssef Zidan

Posted on October 25, 2020

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related