Error Handler Angular 6 HttpClient – catchError + retry – with Node.js/Express example

In the tutorial, we show how to handle error from Angular HttpClient with catchError & retry when request fails on the server, or in case of a poor network connection.

Related posts:
Node.js/Express RestAPIs – Angular 6 HttpClient – Get/Post/Put/Delete requests + Bootstrap 4

Technologies

  • Angular 6
  • RxJS 6
  • Bootstrap 4
  • Visual Studio Code – version 1.24.0
  • Nodejs – v8.11.3

Error Handling

Error Object

Simple Angular HttpClient to request as below:


@Injectable({
  providedIn: 'root'
})
export class CustomerService {
  private customersUrl = 'http://localhost:8080/api/customers';  // URL to web api

  constructor( 
    private http: HttpClient
  ) { }

  getCustomers (): Observable {
    return this.http.get(this.customersUrl);
  }
}

What happens if the request fails on the server, or if a poor network connection?

– Server is die -> console’s logs:

angular-6-error-handling-catch-error + error

– 404 error -> console’s logs:

angular-6-error-handling-catch-error + error-404

– 500 error -> console’s logs:

angular-6-error-handling-catch-error + 500-error

-> HttpClient will return an error object.
We can handle it from Component code with .subcribe:


export class CustomerComponent  implements OnInit {

  ...
  
  getCustomers() {
    return this.customerService.getCustomers()
               .subscribe(
                 customers => { // success path
				  // to do
                  console.log(customers);
                 }, 
				 error => { // error path
					this.error = error
				 }
            );
 }
}

Error Details

Angular HttpClient provides HttpErrorResponse to capture error responses. We can use HttpErrorResponse to build detail user-friendly responses:


import { Injectable } from '@angular/core';
import { HttpErrorResponse } from '@angular/common/http';

export type HandleError =  (operation?: string, result?: T) => (error: HttpErrorResponse) => Observable;

/** Handles HttpClient errors */
@Injectable()
export class HttpErrorHandler {

  constructor(private errorService: ErrorService) { }

  /** Create handleError function that already knows the service name */
  createHandleError = (serviceName = '') => 
    (operation = 'operation', result = {} as T) => this.handleError(serviceName, operation, result);

  /**
   * @param serviceName: name of the data service
   * @param operation: name of the failed operation
   * @param result: optional value to return as the observable result
   */
  handleError (serviceName = '', operation = 'operation', result = {} as T) {

    return (error: HttpErrorResponse): Observable => {
      // Todo -> Send the error to remote logging infrastructure
      console.error(error); // log to console instead

      const message = (error.error instanceof ErrorEvent) ?
        error.error.message :
       `{error code: ${error.status}, body: "${error.message}"}`;
	   
      // -> Return a safe result.
      return of( result );
    };
  }
}

-> Now we pipe returned Observables with the error handler:


export class CustomerComponent  implements OnInit {

  private handleError: HandleError;

  constructor( 
    private http: HttpClient,  
    httpErrorHandler: HttpErrorHandler
  ) { 
    this.handleError = httpErrorHandler.createHandleError('CustomerService');
  }
  ...
  
  getCustomers (): Observable {
	return this.http.get(this.customersUrl)
	  .pipe(
		catchError(this.handleError('getCustomers', []))
	  );
  }

Retry

The RxJS library offers retry operator to automatically re-subscribes to a failed Observable with a specified number of times.

export class CustomerComponent  implements OnInit {

  private handleError: HandleError;

  constructor( 
    private http: HttpClient,  
    httpErrorHandler: HttpErrorHandler
  ) { 
    this.handleError = httpErrorHandler.createHandleError('CustomerService');
  }
  ...
  
  getCustomers (): Observable {
	return this.http.get(this.customersUrl)
	  .pipe(
		retry(3), // retry a failed request up to 3 times
		catchError(this.handleError('getCustomers', [])) // then handle the error
	  );
  }

angular-6-error-handling-catch-error + retry-404-error

Practice

We re-use the source-codes of Angular 6 HttpClient – Node.js/Express RestAPIs tutorial.

How to handle error?
-> In Angular project, we need build a HttpErrorHandler service and ErrorComponent to notify on UI.

– Angular Project as below:

angular-6-error-handling-catch-error + project-structure

– Node.js project:

angular-6-error-handling-catch-error + nodejs-structure

Implement Now ->

Generate HttpErrorHandler, Error service, Error component by cmd:


ng generate service HttpErrorHandler
ng generate service Error
ng generate component Error

HttpErrorHandler Service

Implement the service ./src/app/http-error-handler.service.ts:


import { Injectable } from '@angular/core';
import { HttpErrorResponse } from '@angular/common/http';

import { Observable, of } from 'rxjs';

import { ErrorService } from './error.service';

export type HandleError =  (operation?: string, result?: T) => (error: HttpErrorResponse) => Observable;

/** Handles HttpClient errors */
@Injectable()
export class HttpErrorHandler {

  constructor(private errorService: ErrorService) { }

  /** Create handleError function that already knows the service name */
  createHandleError = (serviceName = '') => 
    (operation = 'operation', result = {} as T) => this.handleError(serviceName, operation, result);

  /**
   * @param serviceName: name of the data service
   * @param operation: name of the failed operation
   * @param result: optional value to return as the observable result
   */
  handleError (serviceName = '', operation = 'operation', result = {} as T) {

    return (error: HttpErrorResponse): Observable => {
      // Todo -> Send the error to remote logging infrastructure
      console.error(error); // log to console instead

      const message = (error.error instanceof ErrorEvent) ?
        error.error.message :
       `{error code: ${error.status}, body: "${error.message}"}`;

      // Todo -> Transforming error for user consumption
      this.errorService.errorMessage = `${serviceName} -> ${operation} failed.\n  Message: ${message}`;
      // -> Return a safe result.
      return of( result );
    };
  }
}

Add HttpErrorHandler service to providers of AppModule module:


import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';

import { HttpErrorHandler } from'./http-error-handler.service';

@NgModule({
  ...
  
  providers: [HttpErrorHandler],
  ...
})
export class AppModule { }

Update Customer Service

./app/customer.service.ts ->


import { Injectable } from '@angular/core';
import { HttpClient, HttpHeaders } from '@angular/common/http';
import { Observable } from 'rxjs';
import { catchError, retry } from 'rxjs/operators';

import { Customer } from './customer';

import { HttpErrorHandler, HandleError } from './http-error-handler.service';

const httpOptions = {
  headers: new HttpHeaders({ 'Content-Type': 'application/json' })
};

@Injectable({
  providedIn: 'root'
})
export class CustomerService {
  private customersUrl = 'http://localhost:8080/api/customers';  // URL to web api
  private handleError: HandleError;

  constructor( 
    private http: HttpClient,  
    httpErrorHandler: HttpErrorHandler
  ) { 
    this.handleError = httpErrorHandler.createHandleError('CustomerService');
  }

  getCustomers (): Observable {
    return this.http.get(this.customersUrl)
    .pipe(
      retry(3),
      catchError(this.handleError('getCustomers', []))
    );
  }

  getCustomer(id: number): Observable {
    const url = `${this.customersUrl}/${id}`;
    return this.http.get(url)    
      .pipe(
        retry(3),
        catchError(this.handleError('addCustomer', null))
      );
  }

  addCustomer (customer: Customer): Observable {
    return this.http.post(this.customersUrl, customer, httpOptions)
      .pipe(
        retry(3),
        catchError(this.handleError('addCustomer', customer))
      );
  }

  deleteCustomer (customer: Customer | number): Observable<{}> {
    const id = typeof customer === 'number' ? customer : customer.id;
    const url = `${this.customersUrl}/${id}`;

    return this.http.delete(url, httpOptions)      
      .pipe(
        retry(3),
        catchError(this.handleError('deleteCustomer', null))
      );
  }

  updateCustomer (customer: Customer): Observable {
    return this.http.put(this.customersUrl, customer, httpOptions)
      .pipe(
        catchError(this.handleError('updateCustomer', null))
      );
  }
}

Error Service

./src/app/error.service.ts ->


import { Injectable } from '@angular/core';

@Injectable({
  providedIn: 'root'
})
export class ErrorService {

  errorMessage: string = "";
  
  constructor() { }
}

Error Handler Component

./app/error/error.component.ts ->


import { Component, OnInit } from '@angular/core';
import { ErrorService }  from '../error.service'

@Component({
  selector: 'app-error',
  templateUrl: './error.component.html',
})
export class ErrorComponent implements OnInit {

  constructor(public errorService: ErrorService) { 
  }

  ngOnInit() {
  }
}

./app/error/error.component.html ->

<div>
  <h3 style="color:red">{{errorService.errorMessage}}</h2>
</div>

Run & Check Results

Server Die

Start Node.js server with cmd -> npm start
Run Angular Client with cmd -> ng serve

-> results:

angular-6-error-handling-catch-error + normal-request

Stop Node.js server. Then make the request again ->

angular-6-error-handling-catch-error + get-all-customers

404 Error

In Node.js project, modify the file customer.routes.js, command out a router:
// router.get('/api/customers', customers.findAll);

Restart Node.js server -> 404 error:

angular-6-error-handling-catch-error + get-all-customer-404-error

500 Error

Now remove above command for the router:
router.get('/api/customers', customers.findAll);.

And change findAll funtion in file ./app/controllers/customer.controller.js as below:

exports.findAll = function(req, res) {
  //  res.json(Object.values(customers));  
  res.status(500).send('error');
};

Restart Node.js server -> 500 error:

angular-6-error-handling-catch-error + 500-ui-error

SourceCode

Angular-6-Http-Client
Node.js-RestAPIs

4 thoughts on “Error Handler Angular 6 HttpClient – catchError + retry – with Node.js/Express example”

  1. Very useful article to learn how to catch error using rxjs. I assume the usage of ErrorService is for sample purpose only: in the component (eg. customer-detail-component) you should not use the “this.errorService.errorMessage” but you should use “result” instead otherwise, since that’s a singleton, you may have a wrong msg due to a previous error from a different observable

Leave a Reply

Your email address will not be published. Required fields are marked *