Author avatar

Zachary Bennett

Handling Errors Reactively With RxJS

Zachary Bennett

  • Aug 3, 2020
  • 5 Min read
  • 230 Views
  • Aug 3, 2020
  • 5 Min read
  • 230 Views
Languages Frameworks and Tools
Front End Web Developer
Client-side Frameworks
Angular

Introduction

Within your Angular app or library, there are often many points at which your code is vulnerable to errors. Event-based programming is core to the JavaScript workflow and many apps are, at their core, focused on handling and responding to events that are triggered by the user. It is precisely the unknown variability surrounding these events that creates many of these errors.

So, how do you handle errors within your Angular app or library? Is there a more functional approach to handling errors rather than the more imperative try/catch approach?

In this guide, you will learn how you can handle all kinds of errors using RxJS and the catchError operator that it provides.

Note: Since RxJS version >=5.5, the catch operator is now named catchError.

Catching Event-based Errors

Let's say you have a SharkComponent that is responsible for registering users' clicks on a page, doing some calculations, and storing the resulting location. This component looks like the following:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import { Component } from '@angular/core';
import { SharkService } from 'app/services/sharks';
import { Shark } from 'app/models/shark';

@Component({
  selector: 'app-sharks',
  template: `
    <ul *ngFor="let shark of sharks$ | async">
        <button (click)="lastBiteLocation$ = shark.bite($event)">{{ shark.name }}</button>
    </ul>
    `
})
export class SharksComponent {
    sharks$: Observable<Shark[]>;
    lastBiteLocation$: Observable<{ x: number, y: number }>;

    constructor(private sharkService: SharkService) {
        this.sharks$ = this.sharkService.getSharks();
    }
}
typescript

Every shark has a bite method located on it that handles the MouseEvent that is passed into the method every time a user clicks on a shark button. The bite method looks like this:

1
2
3
4
5
6
7
bite(event: MouseEvent): Observable<{ x: number, y: number}> {
    // Performing calculations here - getX or getY may throw an error!
    const x = this.getX(event),
          y = this.getY(event);

    return of({ x, y }); // Use RxJs 'of' helper here to create an Observable
}
typescript

But wait--the bite method can throw an error! If an error occurs when you subscribe to the lastBiteLocation$ field, that would not be good! So how do you handle this? Why, with catchError of course! Check out this revamped bite method:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
bite(event: MouseEvent): Observable<{ x: number, y: number}> {
    return of({ x: 0, y: 0 })
            .pipe(
                map(loc => {
                    return {
                        x: this.getX(event),
                        y: this.getY(event)
                    };
                }),
                catchError(error => {
                    // Handle our error here...
                    console.error(error);
                    return of({ x: 0, y: 0 }); // Return our default location!
                });
            );
}
typescript

Now you can safely call bite and rest assured that any errors will be handled. The default {x: 0, y: 0} location will always be handed back to you.

Catching Custom Made Errors

The previous section is very useful. But what if you want the bite method to throw an error if one occurs? Let's say that you want to show the user a custom error message in this case. Well, you can use the RxJS throwError operator for this. You can now change your bite method to this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
bite(event: MouseEvent): Observable<{ x: number, y: number}> {
    return of({ x: 0, y: 0 })
            .pipe(
                map(loc => {
                    return {
                        x: this.getX(event),
                        y: this.getY(event)
                    };
                }),
                catchError(error => {
                    console.error(error);
                    return throwError(new Error('This shark did not bite correctly!'));
                });
            );
}
typescript

There you go! Now you can handle this error upstream in the SharkComponent by subscribing to your lastBiteLocation$ observable like this:

1
2
3
4
5
6
7
8
9
10
    this.lastBiteLocation$
        .subscribe(
            location => {
                // We have a location to handle here!
            },
            error => {
                // Now we can handle our custom bite error and show it in the UI!
                this.biteError = error;
            }
        );
typescript

Conclusion

In this guide, you have learned how to handle potentially error-prone portions of your Angular app or library functionally, and how to reactively use RxJS and the catchError operator. Specifically, you can now be confident in regards to:

  • Using reactive streams to catch unpredictable errors
  • Creating and catching custom made errors

For more tips regarding handling errors in Angular (specifically HTTP errors), you can check out this guide

3