Reputation: 857
I have an rxjs BehaviorSubject
I subscribe to using an async
pipe from Angular 2 and I have a catch
to handle eventual errors it throws.
Problem is, every time I get an error it starts an infinite loop because my catch returns the Observable derived from the BehaviorSubject
and I think the async
pipe resubscribes to the observable when I return the catch
.
The code looks roughly like this:
ListService - is a @Injectable
where I have the BehaviorSubject and the property with the Observable
.
private listSubject: BehaviorSubject<IItem[]>;
public get listObservable() {
return this.listSubject.asObservable()
}
private error(error: IError) {
this.listSubject.error(error);
}
ListComponent - is a @Component
that shows the list observable.
// Template
<items-view [items]="list | async"></items-view>
// Code
public get list() {
return this.listService.listObservable
.catch((error) => {
this.handleError(error);
return this.listService.listObservable;
});
}
As you can see, my catch returns the current observable, as it MUST return an observable. So, what happens is, when I send the this.listSubject.error(error)
the code enters an infinite loop calling the catch
indefinitely because, like I said before, I think that the BehaviourSubject
re-throws the error because the async
pipe re-subscribes to the observable when the catch
returns it.
I tried to return my previous cached array in the error to return an Observable.of(error.cached)
, but I got a whole new set of problems because think the async wasn't subscribed to the BehaviorSubject
anymore.
Like I said before, this is a rough representation of my real code, but the logic is basically that.
I have been trying various different approaches to this but I couldn't manage to get this infinite loop to stop.
Thanks in advance for the help.
Upvotes: 2
Views: 3228
Reputation: 16892
It is a generally bad idea to manually dispatch an error on a Subject, that is supposed to only eject data (like the BehaviorSubject
in your case). The reason is, that when an error occurs on a Subject
, the Subject
is essentially dead -> meaning, that no new data can be ejected on it any more. This is one of the core-concepts of rxjs. Here is a small example:
let stream$ = new Rx.BehaviorSubject(1);
stream$.subscribe(x => console.log("Logging: " + x), e => console.error("Error: " + e)); // logs 1, 2, Error: ...
stream$.next(2);
stream$.error(new Error("Some error message."));
stream$.next(3); // this will have no effect, because the stream is "dead"
stream$.subscribe(x => console.log("Logging 2: " + x), e => console.error("Error: " + e)); // this will just log the error
For your case this means that you did some error-handling, but then just return the "old, dead, error"-Subject - in other words: propagate the error down the stream. (I'm not assuming that handleError()
creates a fresh Subject
, which would be an awful practice anyways.)
In general it means that .error
should only be used for streams that perform a defined operation and have a defined number of results and then complete or throw an error, but not on Subject
s that you want to emit data throughout the complete lifetime of the application.
How to solve this in your case: The quick&dirty (really dirty!!) way would be to use two separate Subjects, one for the data and one for errors (eject with .next
).
The proper fix: Split up your architecture into a data-generation-flow and into a data-store-part.
The lifecycle would look something like this:
Generative Flow
Subscribing Flow
The perfect fix would be to use a ready-to-use thought-through store-architecture like ngrx, however implementing this in an existing project will come with major refactoring requirements.
Upvotes: 4