SHARE
Facebook X Pinterest WhatsApp

RxJS Error Handling Using catchError()

Written By
thumbnail
Rob Gravelle
Rob Gravelle
Mar 18, 2022

Error handling plays an essential part in just about any application, doubly so when dealing with network calls. For that reason, libraries like RxJS include a wide variety of mechanisms for dealing with errors of every conceivable sort in a graceful way. In fact, RxJS lends itself to so many error handling strategies that developers are often unsure which to use for a given situation. This web development tutorial aims to clear up the confusion by providing a few concrete examples that you can follow in your own coding.

Success, Error, Complete ≠ Try, Catch, Finally

On initial observation, the three RxJS subscribe() function parameters bear a close resemblance to the try, catch, finally blocks of traditional JavaScript (JS) error handling. In fact, they are similar in several ways:

  • In both cases, the complete() function and finally block are optional.
  • The error() function and catch block both allow us to handle errors in a graceful manner.
  • Both the error() function and catch block give us the option of handling the error ourselves.

Beyond these similarities, the success, error, and complete functions of RxJS differs from its synchronous JS counterpart in one very important respect:

Whereas the finally block executes no matter what happens before it, the complete() function is only invoked if the stream completes, i.e, if the stream does not error out.

Read: Graceful RxJS Error Handling

We can verify that behavior easily enough by fetching both a valid and invalid URL using the HttpClient’s get method:

import { HttpClient } from '@angular/common/http';
import { Component } from '@angular/core';

@Component({
  selector: 'my-app',
  templateUrl: './app.component.html',
  styleUrls: [ './app.component.css' ],
  providers: [HttpClient]
})
export class AppComponent  {

  constructor(private http: HttpClient) {}

  ngOnInit() {
    const http$ = 
      this.http.get(
        'mattree-node-highlighting-demo?file=main.ts', 
        {responseType: 'text'}
      ); 

    http$.subscribe({
      next: (res) => console.log(res),
      error: (err) => console.error(err),
      complete: () => console.info('complete') 
    });
  }
}

We can see that both the HTML content and ‘complete’ message were printed to the console:

RxJS Error Handling Example

 

Changing the URL parameter to something that fails now only outputs the error:

Error Handling in RxJS

 

Now imagine that you wanted to show a dialog after a POST operation that updates user permissions. You might like to pass along an error message as follows:

constructor(public dialogRef: MatDialog) { }

const dialogConfig = new MatDialogConfig();
this.appService.updatePermissions(this.permissionId, this.permissions)
  .subscribe(
    () => '',
    (err) => dialogConfig.data = { err: err ? err : "Something went wrong! Couldn't save permissions." },
    () => this.dialogRef.open(DialogComponent, dialogConfig)
  );

Knowing what we do now about how subscribe handlers work, it is obvious that the dialog won’t appear when an error is encountered.

Read: RxJS Observables Primer in Angular

Continuing On Error in RxJS Error Handling

To make the above code work as intended, we need to catch the error before the subscribe and send the error state along for handling there. For that purpose, there is the carchError() operator. It can return a new observable (or error) so we can log the error for debugging purposes and then return a Boolean indicating that an error was encountered.

The subscribe() will now receive the error observable so that it can let the dialog know whether or not the save operation was successful:

interface SaveDialogData {
  success?: boolean;
}

this.appService.updatePermissions(this.permissionId, this.permissions)
  .pipe(
    catchError(err => {
      if (err) {
        console.error(err);
      }
      return of(err != null);
    })
  )
  .subscribe((err: boolean) => {
    const dialogConfig = new MatDialogConfig<SaveDialogData>();
    if (err) {
      dialogConfig.data = { success: false };
    }
    this.dialogRef.open(NewsDialogComponent, dialogConfig);
  });

This particular type of error treatment is known as the “Catch and Replace Strategy”, whereby catchError() is employed to provide a replacement Observable that emits a fallback value or values. The trick to making this strategy work is to pass whatever replacement value(s) we want to send along to the of() method. It can accept a variable amount of values in a sequence and then immediately emits a complete notification. It is similar to from(), but does not do any flattening and emits each argument in whole as one next notification.

Here is its signature:

of<T>(...args): Observable<T>

So, even though we are only sending a single Boolean value, we could just as easily have passed an array of values as follows:

return of(err, this.permissionId, this.permissions);

Read: Executing RxJS 6 Observables in Order

Accessing the success Boolean in the Dialog Component

In the DialogComponent class, we can inject the data via the constructor and set public variable accordingly:

export class DialogComponent implements OnInit {
  public success: boolean = true;

  constructor(
    @Inject(MAT_DIALOG_DATA) public data: SaveDialogData
  ) { }

  ngOnInit(): void {
    if (this.data?.success === false) {
      this.success = false;
    }
  }
}

This technique provides an added benefit that the dialog message can be set in the template, making it much easier to translate if necessary:

<h2 mat-dialog-title>Save Permissions</h2>
<div mat-dialog-content>
  {{success ? "Permissions have been successfully saved" : "Something went wrong! Couldn't save permissions."}}
</div>
<div mat-dialog-actions>
    <button mat-button mat-dialog-close>Close</button>
</div> 

The catchError() RxJS Demo

To better understand the catchError() operator, here is a fully editable demo that shows what happens when an API call returns successfully and errors out. This is achieved via two buttons.

The Send valid request emulates a successful Save operation:

RxJS error handling

 

The Send invalid request emulates a failed Save operation:

RxJS How to Handle Errors

 

Conclusion to RxJS Error Handling Using catchError()

While similar the finally block of traditional JavaScript (JS) error handling, the RxJS subscribe() method’s complete() function parameter is not invoked when an asynchronous call errors out. For that reason, the catchError() operator is your best bet for handling errors when you want to continue on whether an error occurs or not.

Read more RxJS and web development tutorials.

Recommended for you...

The Revolutionary ES6 Rest and Spread Operators
Rob Gravelle
Aug 23, 2022
Ahead of Time (AOT) Compilation in Angular
Tariq Siddiqui
Aug 16, 2022
Converting a JavaScript Object to a String
Rob Gravelle
Aug 14, 2022
Understanding Primitive Type Coercion in JavaScript
Rob Gravelle
Jul 28, 2022
HTML Goodies Logo

The original home of HTML tutorials. HTMLGoodies is a website dedicated to publishing tutorials that cover every aspect of being a web developer. We cover programming and web development tutorials on languages and technologies such as HTML, JavaScript, and CSS. In addition, our articles cover web frameworks like Angular and React.JS, as well as popular Content Management Systems (CMS) that include WordPress, Drupal, and Joomla. Website development platforms like Shopify, Squarespace, and Wix are also featured. Topics related to solid web design and Internet Marketing also find a home on HTMLGoodies, as we discuss UX/UI Design, Search Engine Optimization (SEO), and web dev best practices.

Property of TechnologyAdvice. © 2025 TechnologyAdvice. All Rights Reserved

Advertiser Disclosure: Some of the products that appear on this site are from companies from which TechnologyAdvice receives compensation. This compensation may impact how and where products appear on this site including, for example, the order in which they appear. TechnologyAdvice does not include all companies or all types of products available in the marketplace.