Home  >  Angular

Angular RxJS retry

By Arvind Rai, February 06, 2019
This page will walk through Angular RxJS retry operator example. retry operator returns source Observable with the exception of an error. When source Observable calls error then retry operator resubscribe it for the maximum of given number of time. If Observable starts emitting elements and suppose at any point it calls error before completion, then retry operator will resubscribe the source Observable and starts emitting from start again. Suppose we have used retry(3) in our code, it means for any error in source Observable, it will be resubscribed up to 3 times. If in first attempt of resubscribe by retry(3), it completes normally then no other attempt to resubscribe will be made.
RxJS retry operator is imported as following.
import { retry } from 'rxjs/operators'; 
Here on this page we will discuss retry operator with examples step by step.

Technologies Used

Find the technologies being used in our example.
1. Angular 7.0.0
2. Angular CLI 7.0.3
3. TypeScript 3.1.1
4. Node.js 10.3.0
5. NPM 6.1.0
6. RxJS 6.3.3
7. In-Memory Web API 0.6.1

Understanding RxJS retry

The RxJS retry operator subscribes the source Observable if it does not complete due to error. Now find the examples for the given scenarios.
Ex.1: Total max 3 retry, but no success and throw error.
For the demo suppose we have of(1,2,3,4) that will return Observable instance and on subscribe it will emit 1,2,3,4. For the retry demo we will throw error so that this Observable instance does not complete successfully.
of(1,2,3,4).pipe(
  mergeMap(data => {
    if (data === 3) {
	return throwError('Error Occurred.');
    }
    return of(data);
  }),
  retry(3)
).subscribe(res => console.log(res),
   err => {
     console.log("Retried for 3 times.");
     console.error(err);
   }
); 
Output
1,2,1,2,1,2,1,2
Retried for 3 times.
Error occurred. 
Look into the above code. When the data is 3, an error is thrown. But before throwing error on subscribe, retry will try for 3 times maximum to get subscribe it successfully. If no success then finally error will be thrown. The Observable instance will be subscribed 1+ 3 times totally. If in first retry only, source Observable is subscribed with no error, then there will be no more retry.

Ex.2 : Total max 5 retry, but success in 3rd retry
let retryCount = 0;
of('a','b','c','d').pipe(
	mergeMap(data => {
	  if (data === 'c' && retryCount !== 3) {
		retryCount= retryCount + 1;
		return throwError('Error Occurred.');
	  }
	  return of(data);
	}),
	retry(5)
).subscribe(res => console.log(res),
	err => {
	   console.log("Number of retry: "+ retryCount);
	   console.error(err);
	},
	() => console.log("Processing Complete. Number of retry: "+ retryCount)
); 
Output
a b a b a b a b c d
Processing Complete. Number of retry: 3 

In the above code for the first subscribe and retry 1 subscribe and retry 2 subscribe, there are errors. But in third retry, error is not thrown and Observable successfully subscribed and completed.

retry and catchError

Here we will create an example of retry with catchError operator. catchError handles the errors thrown by source Observable. Using catchError we can throw a default value or user defined error.
of("A", "B").pipe(
  switchMap(el => {
	if (el === "B") {
	  throw new Error("Error occurred.");
	}
	return el;
  }),
  retry(2),
  catchError(err => {
	console.error(err.message);
	console.log("Error is handled");
	return of("X"); //return defualt value as X
  })
).subscribe(el => console.log(el),
	err => console.error(err),
	() => console.log("Processing Complete.")
); 
Output
A A A
Error occurred.
Error is handled
X 
In the above code we are throwing error when Observable emits "B". We have passed retry count 2. After all retry, we are still throwing error in our code. Once all the retry is complete then error is caught by catchError operator. From catchError we are throwing source Observable with default element as "X".

retry with HttpClient

When we access data over HTTP, some errors can be handled by just retrying request such as if errors are transient and unlikely to repeat. For example if we have slow network error and our request could not become successful then there are the chances to make request successful if request is retried. Find the sample example.
getBook(id: number): Observable<Book> {
  let url = this.bookUrl + "/" + id;   

  return this.http.get<Book>(url).pipe(
	tap(() => console.log(url)),
	retry(3),  // retry the failed request up to 3 times
	catchError(err => {
		console.log(err);
		return of(null);
	})
  );
} 

Complete Example

book.component.ts
import { Component, OnInit } from '@angular/core';
import { of, throwError } from 'rxjs';
import { switchMap, debounceTime, catchError, retry, mergeMap } 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() {
     //Total max 3 retry, but no success and throw error
      of(1,2,3,4).pipe(
        mergeMap(data => {
          if (data === 3) {
            return throwError('Error Occurred.');
          }
          return of(data);
        }),
        retry(3)
      ).subscribe(res => console.log(res),
         err => {
           console.log("Retried for 3 times.");
           console.error(err);
          }
      );

    //Total max 5 retry, but subscribe success in 2 retry
      let retryCount = 0;
      of('a','b','c','d').pipe(
        mergeMap(data => {
          if (data === 'c' && retryCount !== 3) {
            retryCount= retryCount + 1;
            return throwError('Error Occurred.');
          }
          return of(data);
        }),
        retry(5)
      ).subscribe(res => console.log(res),
         err => {
           console.log("Number of retry: "+ retryCount);
           console.error(err);
          },
          () => console.log("Processing Complete. Number of retry: "+ retryCount)
      );
    //-------------------------
      this.retryAndHandleError();
      this.searchBook();
   }

   retryAndHandleError() {
    of("A", "B").pipe(
      switchMap(el => {
        if (el === "B") {
          throw new Error("Error occurred.");
        }
        return el;
      }),
      retry(2),
      catchError(err => {
        console.error(err.message);
        console.log("Error is handled");
        return of("X"); //return defualt value as X
      })
    ).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(1000),
      switchMap(id => {
        return this.bookService.getBook(id);
      })
    ).subscribe(res => this.book = res,
      err => console.log(err));
    }   
} 
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, tap} from 'rxjs/operators';

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

    constructor(private http: HttpClient) { }
    
    getBook(id: number): Observable<Book> {
      let url = this.bookUrl + "/" + id;   

      return this.http.get<Book>(url).pipe(
        tap(() => console.log(url)),
        retry(3),  // retry the failed request up to 3 times
        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 };
  }
} 
app.module.ts
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { HttpClientModule } from '@angular/common/http';

import { AppComponent }  from './app.component';
import { BookComponent }  from './book.component';

//For InMemory testing
import { InMemoryWebApiModule } from 'angular-in-memory-web-api';
import { TestData } from './test-data';

@NgModule({
  imports: [     
      BrowserModule,
      HttpClientModule,
      FormsModule,
      ReactiveFormsModule,
      InMemoryWebApiModule.forRoot(TestData)		
  ],
  declarations: [
      AppComponent,
      BookComponent
  ],
  providers: [
  ],
  bootstrap: [
      AppComponent
  ]
})
export class AppModule { } 

Run Application

To run the application, find the steps.
1. Download source code using download link given below on this page.
2. Use downloaded src in your Angular CLI application. To install Angular CLI, find the link.
3. Install [email protected]
4. Run ng serve using command prompt.
5. Access the URL http://localhost:4200
We can see output in console as following.
Angular RxJS retry

References

RxJS retry
The RxJS library

Download Source Code

POSTED BY
ARVIND RAI
ARVIND RAI
FIND MORE TUTORILAS


©2019 concretepage.com | Privacy Policy | Contact Us