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
Contents
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<Customer[]> { return this.http.get<Customer[]>(this.customersUrl); } } |
What happens if the request fails on the server, or if a poor network connection?
– Server is die -> console’s logs:
– 404 error -> console’s logs:
– 500 error -> console’s 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<Customer[]> { return this.http.get<Customer[]>(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<Customer[]> { return this.http.get<Customer[]>(this.customersUrl) .pipe( retry(3), // retry a failed request up to 3 times catchError(this.handleError('getCustomers', [])) // then handle the 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:
– Node.js project:
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<Customer[]> { return this.http.get<Customer[]>(this.customersUrl) .pipe( retry(3), catchError(this.handleError('getCustomers', [])) ); } getCustomer(id: number): Observable<Customer> { const url = `${this.customersUrl}/${id}`; return this.http.get<Customer>(url) .pipe( retry(3), catchError(this.handleError('addCustomer', null)) ); } addCustomer (customer: Customer): Observable<Customer> { return this.http.post<Customer>(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<any> { 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:
Stop Node.js server. Then make the request again ->
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:
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:
SourceCode
– Angular-6-Http-Client
– Node.js-RestAPIs
Last updated on March 18, 2019.
Thank you. This finally helped me understand how to handle my errors!
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