Alex Lockwood
Alex Lockwood

Reputation: 83311

Delay emission of observable until the execution of the current Angular tick has finished

In my Angular app, I have a bunch of Observables that emit notifications when certain data changes. Sometimes, I'll run into a case where I need to notify several Observables about data changes in rapid succession:

// Subscribers for observable1 are immediately notified.
observable1.next(...)

// Subscribers for observable2 are immediately notified.
observable2.next(...)

This can sometimes cause annoying bugs, however. I frequently run into situations where the order in which Observables are notified can cause unexpected behavior. For example, the subscribers for observable1 might execute code that assumes the subscribers for observable2 have already been notified.

Basically, I am looking for a way to delay the emission of items to subscribers until the current Angular tick has completed. For example, something like:

observable1.next(...)
observable2.next(...)

// At the end of the current Angular tick, the subscribers
// for observable1 and observable2 are notified.

Or perhaps there is some other rxjs operator that I could use to achieve something similar...

Upvotes: 2

Views: 4351

Answers (1)

msanford
msanford

Reputation: 12247

To provide you with an option that will do what you want (ish).

This is a classic race condition: your subscribers to observable1 and observable2 claim to be independent, but they seem actually to be coupled.

I played with implementations of .delayWhen(), but the only thing that seemed to guarantee that both are up to date before they are used given your current architecture is to .zip() the two Observables at the source, emit the result, and to have your subscribers split them:

console.info('Waiting for Observables...');

const observable1 = Rx.Observable.of([1,2,3])
  .delayWhen(() => Rx.Observable.timer(1000));

const observable2 = Rx.Observable.of(['a', 'b', 'c'])
  .delayWhen(() => Rx.Observable.timer(500));

const merged = Rx.Observable.zip(observable1, observable2).share();

// Where you need the result from 1
merged.subscribe(
  next => {
    console.info("Subscriber 1");
    console.info(next[0]);
  }
);

// Where you need the result from 2
merged.subscribe(
  next => {
    console.info("Subscriber 2");
    console.info(next[1]);
  }
);
<script src="https://unpkg.com/@reactivex/rxjs@5.3.0/dist/global/Rx.min.js"></script>

Looks hacky?

Because it kind of is, but all we're doing is explicitly implementing a coupled state that is already coupled conceptually by the architecture.

The real solution is to somehow change the architecture so that they are not dependent (by, for example, changing the payload returned by whoever produces the two observable's data sets).

This also only works in this fairly trivial example where the total number of Observables is known and are in the same service. It quickly gets very complicated if these two conditions are exceeded.

The overall architecture is another question and outside this scope since we don't have insight into this.

Upvotes: 1

Related Questions