Error Handler Angular 6 HttpClient – catchError + retry – with SpringBoot RestAPIs example

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

Related posts:
Angular 6 HttpClient – Get/Post/Put/Delete requests + SpringBoot RestAPIs + Bootstrap 4

Technologies

  • Angular 6
  • RxJS 6
  • Bootstrap 4
  • Visual Studio Code – version 1.24.0
  • SpringBoot

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-retry-error-spring-boot-restapi---unknown-error-console-logs

– 404 error -> console’s logs:

angular-6-retry-error-spring-boot-restapi---404-error-console-logs

– 500 error -> console’s logs:

angular-6-retry-error-spring-boot-restapi---500-error-console-logs

-> 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 = <T> (operation?: string, result?: T) => (error: HttpErrorResponse) => Observable<T>;

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

  constructor(private errorService: ErrorService) { }

  /** Create handleError function that already knows the service name */
  createHandleError = (serviceName = '') => <T>
    (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<T> (serviceName = '', operation = 'operation', result = {} as T) {

    return (error: HttpErrorResponse): Observable<T> => {
      // 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-retry-error-spring-boot-restapi---retry-error

Practice

We re-use the source-codes of Angular 6 HttpClient – Get/Post/Put/Delete requests + SpringBoot RestAPIs + Bootstrap 4 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-retry-error-spring-boot-restapi---angular-project

– SpringBoot project:

angular-6-retry-error-spring-boot-restapi---spring-boot-project-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 = <T> (operation?: string, result?: T) => (error: HttpErrorResponse) => Observable<T>;

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

  constructor(private errorService: ErrorService) { }

  /** Create handleError function that already knows the service name */
  createHandleError = (serviceName = '') => <T>
    (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<T> (serviceName = '', operation = 'operation', result = {} as T) {

    return (error: HttpErrorResponse): Observable<T> => {
      // 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}}</h3>
</div>

Run & Check Results

Server Die

Start SpringBoot server.
Run Angular Client with cmd -> ng serve

-> results:

angular-6-retry-error-spring-boot-restapi---normal-results

Stop SpringBoot server. Then make the request again ->

angular-6-retry-error-spring-boot-restapi---unknow-error-code

404 Error

In SpringBoot project, modify method getAll in file RestAPIs.java:


@GetMapping(value="/api/customers")
public ResponseEntity  getAll(HttpServletResponse res){
	/*List results = customers.entrySet().stream()
								.map(entry ->entry.getValue())
								.collect(Collectors.toList());
	return results;*/
	res.setStatus(HttpServletResponse.SC_NOT_FOUND);
	
	return null; 
}

Restart SpringBoot server -> 404 error:

angular-6-retry-error-spring-boot-restapi---404-error-code

500 Error

In SpringBoot project, modify method getAll in file RestAPIs.java:

@GetMapping(value="/api/customers")
public ResponseEntity<?>  getAll(HttpServletResponse res){
	/*List<Customer> results = customers.entrySet().stream()
								.map(entry ->entry.getValue())
								.collect(Collectors.toList());
	return results;*/
	res.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
	
	return null; 
}

Restart SpringBoot server -> 500 error:

angular-6-retry-error-spring-boot-restapi---500-error-code

SourceCode

Angular-6-Http-Client-Handler-Retry
SpringBootRestAPIs



By grokonez | January 15, 2019.

Last updated on May 18, 2021.



Related Posts


Got Something To Say:

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

*