Angular / RxJS – Subject and BehaviorSubject error does not propagate to all subscribers and observers if there’s no error handler
I spent quite a bit of time trying to figure out why my angular service did not throw an http error correctly to all components watching it. Some times it would work and some times it would not. This is easier to see by looking at some code.
this.subject = new BehaviorSubject();
this.sub1 = this.subject.subscribe(res => {
console.log("sub 1", res);
});
this.sub2 = this.subject.subscribe(res => {
console.log("sub 2", res);
}, err => {
console.log("sub 2 err", err);
}, res => {
console.log("sub 2 closed");
});
this.sub3 = this.subject.subscribe(res => {
console.log("sub 3", res);
}, err => {
console.log("sub 3 err", err);
}, res => {
console.log("sub 3 closed");
});
this.subject.next("test 1");
this.subject.next("test 2");
this.subject.error(Error("test error"));
// console (Expected)
sub 1 test 1
sub 2 test 1
sub 3 test 1
sub 1 test 2
sub 2 test 2
sub 3 test 2
sub 2 err test error
sub 3 err test error
// console (Actual)
sub 1 test 1
sub 2 test 1
sub 3 test 1
sub 1 test 2
sub 2 test 2
sub 3 test 2
Error: test error
Issue
The issue turns out that one of the angular components subscribed to my service did not have an error handler attached. RxJS decides to throw a global error, and does not propagate the error through all subscribers if this happens.
See this report for more info: https://github.com/ReactiveX/rxjs/issues/2180
The Fix
This was the best I could come up with without modifying RxJS itself. What I’m doing is removing any subscribers without an error handler before throwing the error. This shouldn’t negatively affect anything because when a subject throws an error, it closes all subscriptions anyways.
this.subject = new BehaviorSubject();
this.sub1 = this.subject.subscribe(res => {
console.log("sub 1", res);
});
this.sub2 = this.subject.subscribe(res => {
console.log("sub 2", res);
}, err => {
console.log("sub 2 err", err);
}, res => {
console.log("sub 2 closed");
});
this.sub3 = this.subject.subscribe(res => {
console.log("sub 3", res);
}, err => {
console.log("sub 3 err", err);
}, res => {
console.log("sub 3 closed");
});
this.subject.next("test 1");
this.subject.next("test 2");
// remove all subscribers without an error before calling .error()
this.subject.observers.map((obs, index) => {
if(typeof obs.destination._error !== "function"){
obs.complete();
}
});
this.subject.error(Error("test error"));
Now the output should be as expected.
My google search box was filled with keywords like this:
rxjs subject catch error
angular subscriber not catching error
angular subject throw error
rxjx behavior subject error handler
Bonus Error
core.js:1427 ERROR Error: Uncaught (in promise): TypeError: Cannot read property 'unsubscribe' of undefined
TypeError: Cannot read property 'unsubscribe' of undefined
I started getting this error after using .error() in my service. My component receiving the error would navigate to another page, however the service Subject would still be in error mode. Angular services are not recreated like components are. To fix this, we just need to recreate the subject inside the service as it’s no longer useable once it throws an error.
this.subject = new BehaviorSubject();
this.subject.next("test 1");
this.subject.next("test 2");
this.subject.error(Error("test error"));
// recreate a new subject
this.subject = new BehaviorSubject();