RxJS is the biggest part of Angular. With a well understanding of how to implement error handling the right way with RxJS, you are sure to run with strange problems down the line when an error does occur. By contrast, if you know what you are doing up a cover, you can remove those strange problems and save yourself some debugging distress.
In this blog, we will discuss the type of RxJS observables to be most involved, how to incorrectly handle an error through RxJS, what happens when you do not handle an error applying RxJS and how to precisely handle errors applying RxJS.
Infinite Observables
This blog will be linked with infinite observables, those that you hope to keep getting values from. That is because if you do error handling incorrectly, they stop to be infinite observables and finish, which will be very bad. After all, your app is expecting it to be infinite.
These will be considered:
- DOM Event: A DOM Event ‘keyup’ that you wanna debounce as the user types on the page and then look up through an API.
- NgRx Effect: An NgRx Effect that you hope will forever be listening for dispatched actions
DOM Event Case Study
That first consideration will concentrate on handling DOM events and doing searches based on them. There will be two input boxes that you type in a character's name of Star Wars. While you stop typing for 300 milliseconds and the text is changed. It will search for those names through the Star Wars API and display results. The first input box will keep working after an error. The second input box will halt working after an error.
Here is the interface:
We have gamed this a pretty bit so that if you type “error”, it will search over a wrong URL, therefore creating an error.
Here is the appropriate HTML:
<input class="form-control">
<input class="form-control">
The key up event normally emits through the “next” method of the Subject.
Following is the component code:
import { Component } from '@angular/core';
import { Subject } from 'rxjs';
import { finalize } from 'rxjs/operators';
import { RxJSDemoService } from './rx-jsdemo.service';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
title = 'RxJSErrorHandlingDemo';
searchTerm$ = new Subject<string>();
searchTermError$ = new Subject<string>();
resultsError: any;
results: any;
constructor(
private rxjsDemoService: RxJSDemoService,
) {}
ngOnInit(): void {
this.rxjsDemoService
.searchBadCatch(this.searchTermError$)
.pipe(
finalize(() =>
console.log("searchTermError$ (bad catch) finalize called!")
)
)
.subscribe(results => {
console.log("Got results from search (bad catch)");
this.resultsError = results.results;
});
this.rxjsDemoService
.search(this.searchTerm$)
.pipe(finalize(() => console.log("searchTerm$ finalize called!")))
.subscribe(results => {
console.log("Got results from search (good catch)");
this.results = results.results;
});
}
}
</string></string>
This code essentially will be reporting results to the page and logging either it was called or not. Note that we are calling two various service methods and passing in two several subjects.
Read More: Getting Started With Angular Animations
The error handling code for this case study is in the following rxjsDemoService :
import { Injectable } from '@angular/core';
import { of } from 'rxjs';
import { switchMap, debounceTime, distinctUntilChanged } from 'rxjs/operators';
import { catchError } from 'rxjs/operators';
import { Observable } from 'rxjs';
import { HttpClient } from "@angular/common/http";
@Injectable({
providedIn: 'root'
})
export class RxJSDemoService {
constructor(private http: HttpClient){}
searchBadCatch(terms: Observable<string>) {
return terms.pipe(
debounceTime(300),
distinctUntilChanged(),
switchMap(term => this.searchStarWarsNames(term)),
catchError(error => {
console.log("Caught search error the wrong way!");
return of({ results: null });
})
);
}
search(terms: Observable<string>) {
return terms.pipe(
debounceTime(300),
distinctUntilChanged(),
switchMap(term =>
this.searchStarWarsNames(term).pipe(
catchError(error => {
console.log("Caught search error the right way!");
return of({ results: null });
})
)
)
);
}
private searchStarWarsNames(term) {
let url = `https://swapi.co/api/people/?search=${term}`;
if (term === "error") {
url = `https://swapi.co/apix/people/?search=${term}`;
}
return this.http.get<any>(url);
}
}
</any></string></string>
Bad Error Handling
The searchBadCatch method has our bad error handling code. Just looking at it, it looks fine. It's debouncing for 300 ms, has the distinctUntilChanged to ensure we do not search for the same thing twice in a row. There is a switchMap that calls the searchStarWarsName method and we are catching errors through the catchError method.
If you catch the error through catchError at the first step of the Observables pipe method. It will enable you to handle the error and return one more result in the stream but will then finish the observable stream. And that indicates it won't listen to key up events anymore. Thus, at all costs never allow an error to percolate to this step.
Remark that if catchError is given on the initial level of the Observable pipe method, the finalize operator will be called. You can see this up in the component code.
Following is the code of it:
Never let an error permeate to the level of the red line.
Good Error Handling
The search method has our RxJS finest practice error handling code:
Always put the catchError operator within a switchMap so that it only ends the API call stream and then returns the stream to the switchMap, which remains the Observable.
If you are not calling an API, ensure to add a try or catch block so that you can handle the error in the catch block and not allow it to permeate to the first level pipe. Do not believe your code will never fail, use a try or catch block.
So, you can see in the code that we add a pipe to the searchStarWarsNames call so that within of there, we catchError and therefore not allow the error to permeate to the first level pipe.
And the best practice code is the following:
Always catch errors within the switchMap/mergeMap/concatMap, etc.
Output
Now it is time to see how this works on the website. We can imagine that it works at first. The fun starts when an error is generated from the API call.
First,we will type error in both input boxes as shown:
We will quite it as an exercise for you to see the console output. Now for the actual test cantype in something and get results later handling the error?
Here we see that the first one works and the second one does not work anymore:
The second input box is what we were talking about over a Weird problem in the intro. You would have a difficult time estimating out why your search quit working.
Looking to Hire Angular Developer? Your Search ends here.
NgRX Effect Case Study
The reason we began writing this blog was that we had a weird problem with one of our apps that was through NgRX and Effects. See for information on effects.
The interface is the following:
Nothing desires here:
The success button calls the Star Wars API with "person/1" (Luke Skywalker) and gives the name in the output on the screen.
Error-stops listening button calls the API with an incorrect URL so it generates an error message "catch is done wrong so it stops listening for the effect".
Error-Don't catch error button calls the API with an incorrect URL so it generates an error message "not catching the error".
Error-Keeps Listening button calls the API with an incorrect URL so it generates an error message "properly catching the error so you can click it multiple times".
We will skip the HTML for this because it is just buttons calling component methods. Following is the component code:
import { Component, OnInit } from "@angular/core";
import { catchError } from "rxjs/operators";
import { of, throwError, Observable } from "rxjs";
import { Store, select } from "@ngrx/store";
import { RxjsService } from "./services/rxjs.service";
import * as fromRoot from "./store/reducers";
import {
CallWithoutError,
CallWithError,
CallWithErrorKeepListening,
CallWithErrorNotCaught,
EffectReturnTest
} from "./store/app.actions";
@Component({
selector: "app-root",
templateUrl: "./app.component.html",
styleUrls: ["./app.component.css"]
})
export class AppComponent implements OnInit {
title = "RxJS Playground";
name$: Observable<string>;
someString$: Observable<string>;
constructor(
private rxjsService: RxjsService,
private store: Store<fromroot.state>
) {}
ngOnInit(): void {
this.rxjsService.subject
.pipe(
catchError(error => {
console.log("Error in catchError", error);
return of(error);
})
)
.subscribe(
value => {
console.log("Subject value:", value);
},
error => console.log("Error!", error)
);
this.name$ = this.store.pipe(select(fromRoot.getName));
this.someString$ = this.store.pipe(select(fromRoot.getSomeString));
}
pokeSubject(value: boolean) {
this.rxjsService.nextSubject(value);
}
errorSubject() {
this.rxjsService.errorSubject();
}
ngrxSuccess() {
this.store.dispatch(new CallWithoutError());
}
ngrxError() {
this.store.dispatch(new CallWithError());
}
ngrxErrorKeepListening() {
this.store.dispatch(new CallWithErrorKeepListening());
}
ngrxErrorDontCatch() {
this.store.dispatch(new CallWithErrorNotCaught());
}
ngrxEffectReturnTest(actionNum) {
this.store.dispatch(new EffectReturnTest(actionNum));
}
}
</fromroot.state></string></string>
The error handling excellent, evil, and ugly is in the effected code.
CallWithoutError Effect
Following is our success case:
This individual will work each time but if it failed, it would remain working because the catchError is inside the http.get pipe. For the resolution case, the SetName reducer will add the “name” to the store. The UI plucks that up and displays it.
CallWithError Effect
This effect will call the API with the wrong URL so an error is caused. The error handling is done wrongly so once called, this will never work repeatedly unless the app is refreshed.
In this case, the catchError at the first level of this action$.pipe will get called, thus ending the effect since its Observable stream will end. This is much like in the case study above through many RxJS Observables. We should see the message "Error-You're Fasted" on the page after clicking it. If we try clicking the button return, it will not fire the effect.
Following is the output for this:
CallWithErrorKeepListening Effect
This effect will call the API through the incorrect URL so an error is generated. Despite that, it will handle the error properly so that it can be called again.
The correct way to handle the RxJS error is by putting the catchError within the http.get pipe. It will end the http.get observable but that does not matter because it's a finite observable anyways and only emits one value. While it returns the SetName action, the switchMap will release it and remain the Observable stream. Notice that the finalize there will never be called.
Following is the output of this:
CallWithErrorNotCaughtEffect
This our last effect that describes what happens if we do not catch the error? It behaves the same way as if we handled the error incorrectly. It is just that you are not looking into that error stream.
Also, the name on the UI will not be set after you aren't calling setName in the catchError operator. So, you will moreover not see any output if it was the first button clicked or you will see the last name that was set. Another mysterious problem that would be difficult to debug.
Conclusion
As you can tell from this blog, knowing how to appropriately handle RxJS errors in your Angular app will help you to prevent those weird problems you could see when your infinite Observable stream ends unexpectedly. Through this knowledge, you should be able to make sure that your infinite observables never and unless you decide they are finished.
Top comments (0)