1
votes

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.

1
What is the type of error that get's thrown and what do you expect your code to do in the case of an error?KwintenP
Any error, I pass the error using that error function in the List service, even if I just pass a string I'll have the problem. But usually I pass api response errors.Eric.M

1 Answers

3
votes

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 Subjects 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

  1. Some Event (e.g. a Button-Click, or some Timebased Event)
  2. Service.generateOrFetchData().handleErrors()
  3. StoreService.someSubj.next(data) - (step 3 could be optional, depending on how you want to handle errors in step 2)

Subscribing Flow

  1. UI Subscribes to StoreService.someSubj
  2. UI automatically updates whenever new data is ejected, no error-handling required

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.