Open In App

Error Handling in Observables – Angular

Last Updated : 17 Apr, 2024
Improve
Improve
Like Article
Like
Save
Share
Report

In Angular applications, we often work with asynchronous data streams, which are represented by Observables. While Observables provide a powerful way to handle asynchronous operations, it’s important to handle errors that may occur during the subscription to prevent your application from crashing.

Error handling in Observables is a crucial aspect of building robust and reliable Angular applications. When an error occurs, the Observable will terminate, and any subsequent emissions will be ignored. If the error is not handled properly, it can lead to unexpected behavior or even application crashes.

Approach for handling errors in Observables

Angular provides several ways to handle errors in Observables. Here are the main approaches:

1. catch Operator

The catch operator is used to handle errors in Observables. When an error occurs, the catch operator is responsible for catching and handling that error. It takes an error handler function as an argument, which allows you to handle the error or return a new Observable.

Syntax:

import { catchError } from 'rxjs/operators';

observable.pipe(
  catchError((error) => {
    // Handle the error
    return fallbackValue; // or throwError('Custom error message')
  })
);

2. throwError and catchError

The throwError utility function is used to create an Observable that emits a specified error. It’s often used in combination with the catchError operator, which allows you to handle the error and optionally return a new Observable or a default value.

Syntax:

import { throwError } from 'rxjs';

throwError('Custom error message');

3. retry Operator

The retry operator is used to retry an Observable a specified number of times in case of an error. It takes a count argument, which determines the number of times the Observable should be retried before propagating the error.

Syntax:

import { retry } from 'rxjs/operators';

observable.pipe(
  retry(3) // Retry the observable up to 3 times
);

4. retryWhen Operator

The retryWhen operator is similar to the retry operator, but it provides more flexibility and control over the retry logic. It takes a function that receives an Observable of error notifications and returns an Observable that signals when to retry.

Syntax:

import { retryWhen, delay, take } from 'rxjs/operators';

observable.pipe(
  retryWhen(errors => errors.pipe(
    delay(1000), // Delay retry attempts by 1 second
    take(3) // Retry the observable up to 3 times
  ))
);

5. finalize Operator

The finalize operator is used to execute some cleanup logic when the Observable completes, regardless of whether it completed successfully or with an error.

Syntax:

import { finalize } from 'rxjs/operators';

observable.pipe(
  finalize(() => {
    // Perform cleanup actions
  })
);

Example:

To better understand these approaches, let’s create an Angular application and implement each one individually.

Step 1: Create a new Angular application using the Angular CLI:

ng new error-handling-demo

Step 2: Navigate to the project directory

cd error-handling-demo

Step 3: Create a service

ng generate service data

Folder Structure:

Screenshot-2024-04-10-180503

Project structure

Code Example:

HTML
<!-- app.component.html -->

<h1>Error Handling in Observables</h1>
<button (click)="useCatchOperator()">Use catch Operator</button>
<button (click)="useThrowErrorAndCatchError()">Use throwError and catchError</button>
<button (click)="useRetryOperator()">Use retry Operator</button>
<button (click)="useRetryWhenOperator()">Use retryWhen Operator</button>
<button (click)="useFinalizeOperator()">Use finalize Operator</button>
JavaScript
//data.service.ts

import { Injectable } from '@angular/core';
import { Observable, throwError } from 'rxjs';

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

    constructor() { }

    getData(): Observable<string> {
        return throwError('Simulated error occurred');
    }
}
JavaScript
//app.component.ts

import { Component } from '@angular/core';
import { RouterOutlet } from '@angular/router';
import { DataService } from './data.service';
import { catchError, delay, finalize, retry, retryWhen, tap, throwError } from 'rxjs';

@Component({
    selector: 'app-root',
    standalone: true,
    imports: [RouterOutlet],
    templateUrl: './app.component.html',
    styleUrl: './app.component.css'
})
export class AppComponent {
    constructor(private dataService: DataService) { }

    useCatchOperator() {
        this.dataService.getData()
            .pipe(
                catchError(error => {
                    console.error('Error caught:', error);
                    return throwError('Error handled using catch operator');
                })
            )
            .subscribe(
                data => console.log(data),
                error => console.error('Error:', error)
            );
    }

    useThrowErrorAndCatchError() {
        this.dataService.getData()
            .pipe(
                catchError(error => {
                    console.error('Error caught:', error);
                    return throwError('Error handled using catchError');
                })
            )
            .subscribe(
                data => console.log(data),
                error => console.error('Error:', error)
            );
    }

    useRetryOperator() {
        this.dataService.getData()
            .pipe(
                retry(3)
            )
            .subscribe(
                data => console.log(data),
                error => console.error('Error:', error)
            );
    }

    useRetryWhenOperator() {
        this.dataService.getData()
            .pipe(
                retryWhen(errors => errors.pipe(
                    tap(error => console.error('Retrying after error:', error)),
                    delay(2000)
                ))
            )
            .subscribe(
                data => console.log(data),
                error => console.error('Error:', error)
            );
    }

    useFinalizeOperator() {
        this.dataService.getData()
            .pipe(
                finalize(() => console.log('Finalize executed'))
            )
            .subscribe(
                data => console.log(data),
                error => console.error('Error:', error)
            );
    }
}

To start the application run the following command.

ng serve

Output

gifi

Make sure to import the required RxJS operators in the component file for each approach you want to implement.
By running the application (ng serve –open) and clicking the respective buttons, you can see the different error handling approaches in action and observe the console output.



Like Article
Suggest improvement
Previous
Next
Share your thoughts in the comments

Similar Reads