HTTP Error Handling In Angular

HTTP Error Handling In Angular

In this article, we learn about how to handle HTTP errors in Angular. When an HTTP action encounters an error, Angular first encapsulates it in a httpErrorResponse object before returning it. Either in our component class, the data service class, or globally, we catch the httpErrorResponse. The Angular HTTP Interceptor is used to handle global HTTP errors.

HttpErrorResponse

Before providing the errors to our app, the HttpClient gathers them and packages them in a generic HttpErrorResponse. The underlying error object is contained in the HttpErrorResponse's error attribute. Additionally, it gives more information about how the HTTP layer was functioning at the time the problem occurred.

There are two groups of HTTP failures. The error and error answer could both come from the back-end server. A request might not be generated by the client-side code, which would result in an error (ErrorEvent objects).

For a number of reasons, the server might deny the request. The HTTP Status Codes Unauthorized (401), Forbidden (403), Not Found (404), Internal Server Error (500), etc. are returned whenever this occurs. The error response is assigned by Angular to the HttpErrorResponse's error attribute.

The error can also be produced by client-side code. The error could be the result of a network issue, a problem with the HTTP request itself, or an exception produced by an RxJS operator. JavaScript ErrorEvent objects are generated by these errors. The error property of the HttpErrorResponse receives the ErrorEvent object from Angular.

The HTTP Module always returns the generic HttpErrorResponse in both scenarios. To determine the type of mistake, we will examine the error attribute.

Catching Errors in HTTP Request

The three locations where we can catch HTTP errors are as follows.

  • Component
  • Service
  • Globally

Catch Errors in Component

Please see our Angular HTTP Get Request article. In order to obtain the list of Repositories, we built a GitHubService and sent a GET call to the GitHub API. The getRepos() method from the service is as follows. To cause an error, we purposefully altered the URL to be (uersY).

getRepos(userName: string): Observable<any> {
   return this.http.get(this.baseURL + 'usersY/' + userName + '/repos')
}

In the component class, we subscribe to the httpClient.get method.

public getRepos() {
  this.loading = true;
  this.errorMessage = "";
  this.githubService.getReposCatchError(this.userName)
    .subscribe(
      (response) => {                           //Next callback
        console.log('response received')
        this.repos = response;
      },
      (error) => {                              //Error callback
        console.error('error caught in component')
        this.errorMessage = error;
        this.loading = false;
  
        //throw error;   //You can also throw the error to a global error handler
      }
    )
}

There are three callback arguments in the subscribe method.

.subscribe(success, error, completed); 

The first callback success is triggered by the observable when an HTTP request is successful in returning a response. When the observable completes without encountering any errors, the third call back finished is triggered.

When an error occurs with an HTTP request, the second callback error is triggered. Here, we deal with errors by identifying their type and responding appropriately. It receives an error object of the HttpErrorResponse type.

(error) => {                              //Error callback
  console.error('error caught in component')
  this.errorMessage = error;
  this.loading = false;
}

Catch Errors in Service

Using the catchError Operator as demonstrated below, we can also catch problems in the service that generates the HTTP Request. You can throw the error back to the component for additional handling after managing it.

getRepos(userName: string): Observable<repos[]> {
  return this.http.get<repos[]>(this.baseURL + 'usersY/' + userName + '/repos')
    .pipe(
      catchError((err) => {
        console.log('error caught in service')
        console.error(err);

        //Handle the error here

        return throwError(err);    //Rethrow it back to component
      })
    )
}

Catch error globally using HTTP Interceptor

Different types of errors could happen to us. However, some of those mistakes happen with every HTTP request. As An Example
  • You do not have permission to use the API Service,
  • You have permission, but you are not allowed to access this resource.
  • The API End Point is incorrect or nonexistent.
  • network error
  • Server down

Although our app may contain numerous such services or components, we can check for all of these issues in the service or component. It is inefficient and prone to error to check for frequent mistakes in each and every technique.

The Correct course of action is to confine this component's or service's handling of errors to those related to this API call and consolidate all other problems into a single location. We employ the HTTP Interceptor at this point.

Using the Angular Providers, we establish the HTTP Interceptor service and register it globally at the root module. Once defined, it will stop every HTTP request that is made to the app. It intercepts both the HTTP request that we send and the HTTP response that it receives. This makes it the perfect location to identify and correct all typical mistakes.

By developing a Global Service class that implements the HttpInterceptor Interface, we can create the interceptor. The intercept method in that service will then be overridden.

The code below demonstrates a straightforward GlobalHttpInterceptorService.

import {Injectable} from "@angular/core";
import {HttpEvent, HttpHandler, HttpInterceptor,HttpRequest,HttpResponse,HttpErrorResponse} from '@angular/common/http';
import {Observable, of, throwError} from "rxjs";
import {catchError, map} from 'rxjs/operators';
 
@Injectable()
export class GlobalHttpInterceptorService implements HttpInterceptor {
    
  constructor(public router: Router) {
  }
 
  intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
 
    return next.handle(req).pipe(
      catchError((error) => {
        console.log('error is intercept')
        console.error(error);
        return throwError(error.message);
      })
    )
  }
}

Using the catchError RxJS operator, the Error is cached. We then use the throwError function to re-throw it to the subscriber.

Using the RxJs pipe operator, the catchError is added to the request pipeline. When an error occurs in an HTTP request, it is caught and the catchError function is called. You can handle the error inside the catchError function, and then use throwError to send it to the service.

Using the injection token HTTP INTERCEPTORS, we then register the interceptor in the root module's Providers array. Keep in mind that you can offer many Interceptors (multi: true).

providers: [
    GitHubService,
    { provide: HTTP_INTERCEPTORS, useClass: GlobalHttpInterceptorService, multi: true  }
]

Errors Handling in HTTP

The following step is to deal with the errors.

We can act appropriately based on the status codes that the server-side problems report. For instance, we can send the user to the login page if the status code is 401 Unauthorized, retry the process if the status code is 408 Request Timeout, etc.

The example code that follows demonstrates how to search for status codes 401 and 403 and then redirect to the login page.

if (error instanceof HttpErrorResponse) {
    if (error.error instanceof ErrorEvent) {
        console.error("Error Event");
    } else {
        console.log(`error status : ${error.status} ${error.statusText}`);
        switch (error.status) {
            case 401:      //login
                this.router.navigateByUrl("/login");
                break;
            case 403:     //forbidden
                this.router.navigateByUrl("/unauthorized");
                break;
        }
    } 
} else {
    console.error("some thing else happened");
}
return throwError(error);

You can merely request that the user try the operation again for server faults with status codes of 5XX. You can accomplish this by displaying an alert box, sending the user to a specific page, or displaying the error message at the top of the page while sending the user to a unique service called AlertService.

You can just dump it back to the service if there are any further issues.

return throwError(error);

The error can either be handled further in service or returned to the component.

The user must see the error message displayed by the component. In Angular, you can also throw it back to a global error handler.

.subscribe(
   (response) => {
      this.repos = response;
   },
   (error) => {
      //Handle the error here
      //If not handled, then throw it
      throw error; 
   }
)

HTTP Error handling example

The complete code of this example

Add the following code under the app.component.html:

<h1 class="heading"><strong>Angular HTTP</strong>Error Example</h1>
 
<div class="form-group">
  <label for="userName">GitHub User Name</label>
  <input type="text" class="form-control" name="userName" [(ngModel)]="userName">
</div>
 
<div class="form-group">
  <button type="button" (click)="getRepos()">Get Repos</button>
</div>
 
<div *ngIf="loading">loading...</div>
 
<div *ngIf="errorMessage" class="alert alert-warning">
  <strong>Warning!</strong> {{errorMessage | json}}
</div>
 
<table class='table'>
  <thead>
    <tr>
      <th>ID</th>
      <th>Name</th>
      <th>HTML Url</th>
      <th>description</th>
    </tr>
  </thead>
  <tbody>
    <tr *ngFor="let repo of repos;">
      <td>{{repo.id}}</td>
      <td>{{repo.name}}</td>
      <td>{{repo.html_url}}</td>
      <td>{{repo.description}}</td>
    </tr>
  </tbody>
</table> -
 
<pre>{{repos | json}}</pre>

Add the following code under the app.component.ts:

import { Component } from '@angular/core';
 
import { GitHubService } from './github.service';
import { repos } from './repos';
 
@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
})
export class AppComponent {
  userName: string = "TheCodePoints"
  repos: repos[];
 
  loading: boolean = false;
  errorMessage;
 
  constructor(private githubService: GitHubService) {
  }
 
  public getRepos() {
    this.loading = true;
    this.errorMessage = "";
    this.githubService.getReposCatchError(this.userName)
      .subscribe(
        (response) => {                           //Next callback
          console.log('response received')
          this.repos = response;
        },
        (error) => {                              //Error callback
          console.error('error caught in component')
          this.errorMessage = error;
          this.loading = false;
 
          throw error;
        }
      )
  }
}

Add the following code under the github.service.ts:

import { Injectable } from '@angular/core';
 
import { HttpClient, HttpParams, HttpHeaders } from '@angular/common/http';
import { Observable, throwError } from 'rxjs';
import { map, catchError } from 'rxjs/operators';
 
import { repos } from './repos';
 
@Injectable( {providedIn:'root'})
export class GitHubService {
 
  baseURL: string = "https://api.github.com/";
 
  constructor(private http: HttpClient) {
  }
 
  //Any Data Type
  getRepos(userName: string): Observable<any> {
    return this.http.get(this.baseURL + 'usersY/' + userName + '/repos')
  }
 
 
  //With catchError
  getReposCatchError(userName: string): Observable<repos[]> {
    return this.http.get<repos[]>(this.baseURL + 'usersY/' + userName + '/repos')
      .pipe(
        catchError((err) => {
          console.log('error caught in service')
          console.error(err);
          return throwError(err);
        })
      )
  }
 
}

Add the following code under the global-http-Interceptor.service.ts:

import { Injectable } from "@angular/core";
import { HttpEvent, HttpHandler, HttpInterceptor, HttpRequest, HttpErrorResponse } from '@angular/common/http';
import { Observable, of, throwError } from "rxjs";
import { catchError, map } from 'rxjs/operators';
import { Router } from '@angular/router';
 
@Injectable()
export class GlobalHttpInterceptorService implements HttpInterceptor {
 
  constructor(public router: Router) {
  }
 
  //1.  No Errors
  intercept1(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
 
    return next.handle(req).pipe(
      catchError((error) => {
        console.log('error in intercept')
        console.error(error);
        return throwError(error.message);
      })
    )
  }
 
  //2. Sending an Invalid Token will generate error
  intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
 
    const token: string = 'invald token';
    req = req.clone({ headers: req.headers.set('Authorization', 'Bearer ' + token) });
 
    return next.handle(req).pipe(
      catchError((error) => {
        console.log('error in intercept')
        console.error(error);
        return throwError(error.message);
      })
    )
  }
 
  intercept3(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
 
    const token: string = 'invald token';
    req = req.clone({ headers: req.headers.set('Authorization', 'Bearer ' + token) });
 
    return next.handle(req).pipe(
      catchError((error) => {
 
        let handled: boolean = false;
        console.error(error);
        if (error instanceof HttpErrorResponse) {
          if (error.error instanceof ErrorEvent) {
            console.error("Error Event");
          } else {
            console.log(`error status : ${error.status} ${error.statusText}`);
            switch (error.status) {
              case 401:      //login
                this.router.navigateByUrl("/login");
                console.log(`redirect to login`);
                handled = true;
                break;
              case 403:     //forbidden
                this.router.navigateByUrl("/login");
                console.log(`redirect to login`);
                handled = true;
                break;
            }
          }
        }
        else {
          console.error("Other Errors");
        }
 
        if (handled) {
          console.log('return back ');
          return of(error);
        } else {
          console.log('throw error back to to the subscriber');
          return throwError(error);
        }
 
      })
    )
  }
}

Add the following code under the global-error-handler.service.ts:

import { ErrorHandler, Injectable, Injector } from '@angular/core';
import { HttpErrorResponse } from '@angular/common/http';
import { throwError } from 'rxjs';
 
@Injectable()
export class GlobalErrorHandlerService implements ErrorHandler {
 
  constructor() {
  }
 
 
  handleError(error: Error | HttpErrorResponse) {
    console.log('GlobalErrorHandlerService')
    console.error(error);
  }
 
}

Add the following code under the app.module.ts:

import { BrowserModule } from '@angular/platform-browser';
import { NgModule ,ErrorHandler } from '@angular/core';
import { HttpClientModule,HTTP_INTERCEPTORS} from '@angular/common/http';
import { FormsModule } from '@angular/forms';
 
import { AppComponent } from './app.component';
 
import { GlobalHttpInterceptorService} from './global-http-Interceptor.service';
import { AppRoutingModule } from './app-routing.module';
import { GlobalErrorHandlerService } from './global-error-handler.service';
 
 
 
@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    HttpClientModule,
    FormsModule,
    AppRoutingModule
  ],
  providers: [
    { provide: HTTP_INTERCEPTORS,    useClass: GlobalHttpInterceptorService,    multi: true  },
    { provide: ErrorHandler, useClass:GlobalErrorHandlerService}
],
  bootstrap: [AppComponent]
})
export class AppModule { }

Add the following code under the app-routing.module.ts:

import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
 
 
const routes: Routes = [];
 
@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})
export class AppRoutingModule { }

Conclusion

HTTP Interceptors allow you to detect HTTP Errors and respond properly. Check the HTTP status codes and respond appropriately by forwarding to the login page, an error page, or returning the error to the subscriber for additional treatment.

I hope this article helps you and you will like it.👍

If you have any doubt or confusion then free to ask in the comment section.

Post a Comment

Previous Post Next Post