Angular catchError

By Arvind Rai, January 24, 2024
This page will walk through Angular RxJS catchError example. RxJS catchError operator catches the error thrown by Observable and handles it by returning a new Observable or throwing user defined error. catchError is the pipeable operator and it is used within pipe function of Observable. The parameter of catchError is a function that takes error as argument and returns Observable instance. catchError is imported as following.
import { catchError } from 'rxjs/operators'; 
Here we will provide catchError examples. We will understand the difference between catchError and subscribe error callback and we will also provide catchError examples with throwError and retry operators.

1. Observable and catchError

Observable works asynchronously and once there is an error in Observable for any element then it stops emitting other values. To handle Observable error, we can use catchError operator. We use catchError for logging a proper message and to throw user defined error or instance of Observable with default data. Using catchError does not mean that Observable will emit other values which have not been emitted because of error. Let us understand by example.
Look into the code.
of("A", "B", "C", "D", "E").pipe(
  map(el => {
    if (el === "C") {
	throw new Error("Error occurred.");
    }
    return el;
  })
).subscribe(el => console.log(el),
  	    err => console.error(err),
	    () => console.log("Processing Complete.")
); 
Find the output.
A 
B 
Error: "Error occurred." 
In the above code for the element "C", it throws error. The error is caught by subscribe. In the subscribe, first callback is to get result, second callback is to log error if any, third callback is to log completion. Once source Observable throws error, it stops emitting rest of the values it has.
In subscribe if second callback for error executes then the third callback for completion does not execute and if third callback for completion executes, it means there is no error or error is handled by catchError in source Observable. If we throw error from catchError then in subscribe, second callback for error will execute and third callback for completion will not execute.
If we want to handle error before subscribe then we need to use catchError that handles the error in source Observable and returns new instance of Observable that can be subscribed.
of("A", "B", "C", "D", "E").pipe(
 map(el => {
   if (el === "C") {
	throw new Error("Error occurred.");
   }
   return el;
 }),
 catchError(err => {
   console.error(err.message);
   console.log("Error is handled");
   return of("Z");
 })
).subscribe(el => console.log(el),
	    err => console.error(err),
	    () => console.log("Processing Complete.")
); 
Find the output.
A
B
Error occurred.
Error is handled
Z
Processing Complete. 

2. catchError and throwError

After caching error either we can return Observable instance with default data or can throw error using RxJS throwError. throwError emits error notification immediately. Find the code snippet using catchError and throwError.
of("A", "B", "C", "D", "E").pipe(
 map(el => {
   if (el === "C") {
	 throw new Error("Error occurred.");
   }
   return el;
 }),
 catchError(err => {
   console.error(err.message);
   console.log("Error is handled");
   return throwError("Error thrown from catchError");
 })
).subscribe(el => console.log(el),
	   err => console.error(err),
	   () => console.log("Processing Complete.")
); 
Find the output.
A
B
Error occurred.
Error is handled
Error thrown from catchError 
From the catchError block we have returned error using throwError. The error message is shown by error callback of subscribe.

3. retry and catchError

RxJS provides retry operator that resubscribes the Observable for the given number of count when there is an error. Before throwing error Observable is resubscribed for the given number of count by retry operator and if still there is an error, then error is thrown. retry is useful to hit the URL many times. It is possible that because of network bandwidth, URL does not return successful data in one time and when it reties, it may return data successfully. If after retying still there is error in Observable then catchError can be used to return Observable with user defined default data.
Find the sample code to use retry with catchError.
getBook(id: number): Observable<Book> {
  return this.http.get<Book>(this.bookUrl + "/" + id).pipe(
     retry(3),
     catchError(err => {
 	  console.log(err);
	  return of(null);
     })
  );
} 

4. Complete Example

book.component.ts
import { Component, OnInit } from '@angular/core';
import { Observable, of, pipe, throwError } from 'rxjs';
import { map, switchMap, debounceTime, catchError } from 'rxjs/operators';

import { BookService } from './book.service';
import { Book } from './book';
import { FormControl, FormBuilder, FormGroup } from '@angular/forms';

@Component({
   selector: 'app-book',
   template: `
    <h3>Search Book</h3>
    <form [formGroup]="bookForm">
      ID: <input formControlName="bookId">
    </form>
    <br/>
    <div *ngIf="book">
      Id: {{book.id}}, Name: {{book.name}}, Category: {{book.category}}
    </div>
   `
})
export class BookComponent implements OnInit { 
   book: Book;
   constructor(private bookService: BookService, private formBuilder: FormBuilder) { }
   ngOnInit() {
      this.searchBook();

      of("A", "B", "C", "D", "E").pipe(
        map(el => {
          if (el === "C") {
            throw new Error("Error occurred.");
          }
          return el;
        }),
        catchError(err => {
          console.error(err.message);
          console.log("Error is handled");
          return throwError("Error thrown from catchError");
        })
      ).subscribe(el => console.log(el),
                 err => console.error(err),
                 () => console.log("Processing Complete.")
      );

   }
   bookId = new FormControl(); 
   bookForm: FormGroup = this.formBuilder.group({
      bookId: this.bookId
     }
   );
   searchBook() {
    this.bookId.valueChanges.pipe(
      debounceTime(500),
      switchMap(id => {
        console.log(id);
        return this.bookService.getBook(id);
      })
    ).subscribe(res => this.book = res);
   }
} 
book.service.ts
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable, of } from 'rxjs';
import { Book } from './book';
import { catchError, retry } from 'rxjs/operators';

@Injectable({
    providedIn: 'root'
})
export class BookService {
    bookUrl = "/api/books";	

    constructor(private http: HttpClient) { }
    
    getBook(id: number): Observable<Book> {
      return this.http.get<Book>(this.bookUrl + "/" + id).pipe(
          retry(3),
          catchError(err => {
              console.log(err);
              return of(null);
          })
      );
    }    
} 
book.ts
export interface Book {
   id: number;
   name: string;
   category: string;
} 
app.component.ts
import { Component } from '@angular/core';

@Component({
   selector: 'app-root',
   template: `
    <app-book></app-book>
   `
})
export class AppComponent { 
} 
test-data.ts
import { InMemoryDbService } from 'angular-in-memory-web-api';

export class TestData implements InMemoryDbService {
  createDb() {
    let bookDetails = [
      { id: 101, name: 'Angular by Krishna', category: 'Angular' },
      { id: 102, name: 'Core Java by Vishnu', category: 'Java' },
      { id: 103, name: 'NgRx by Rama', category: 'Angular' }
    ];
    return { books: bookDetails };
  }
} 

Find the print screen of the output.
Angular catchError

5. References

RxJS catchError
Angular: The RxJS library

6. Download Source Code

POSTED BY
ARVIND RAI
ARVIND RAI
LEARN MORE








©2024 concretepage.com | Privacy Policy | Contact Us