12

I would like to be able to watch multiple observables and perform some action when any of them change. Similar to how zip works, but without needing each observable to change its value. Similarly forkJoin is not suitable as it only fires when all watched observables have fired.

eg. ideally when any observable value one$, two$ or three$ changes the subscribe function should trigger giving the current state of the Observable. at present I am using BehavourSubject's to the value should be accessible using BehaviourSubject.getValue()

magicCombinator(
  one$,
  two$,
  three$,
).subscribe(([ one, two, three ]) => {
  console.log({ one, two, three });
})

Does a combinator like this exist?

Here is a stackblitz with example code if you need.

Current working code that combines the observables into a single stream and caches the results of each stream into BehavourSubjects.

const magicCombinator = <T = any>(...observables: Observable<T>[]) =>
  new Observable((subscriber: Subscriber<T>) => {
    // convert the given Observables into BehaviourSubjects so we can synchronously get the values
    const cache = observables.map(toBehavourSubject);
    // combine all the observables into a single stream
    merge(...observables)
      // map the stream output to the values of the BehavourSubjects
      .pipe(map(() => cache.map(item => item.getValue())))
      // trigger the combinator Observable
      .subscribe(subscriber);
  });
synthet1c
  • 6,152
  • 2
  • 24
  • 39
  • 3
    `combineLatest` and pipe each source Observable with `startWith()` if you don't want to wait until all of them emit at least once – martin May 21 '21 at 08:25
  • 1
    https://stackoverflow.com/questions/47550911/combinelatest-first-event-not-firing/47551006#47551006 – martin May 21 '21 at 08:26
  • I didn't understand this at first, but its actually a really good solution! Thanks for your input! – synthet1c May 21 '21 at 14:53

1 Answers1

7

That is what you need

merge

Creates an output Observable which concurrently emits all values from every given input Observable

see merge doc

You can merge all observables together and listen with a single subscription to values emitted from each of them.

I would like to be able to watch multiple observables and perform some action when any of them change. Similar to how zip works, but without needing each observable to change its value.

Obsevables do not change value and do not keep state. They just emit values until they complete and then get closed.

If you want to be able to have all the emitted values at a given time then you should go with ReplaySubject. good article about replay subjects But with that you need to make a new subscription each time you want to retrieve all emitted values again.

Panagiotis Bougioukos
  • 15,955
  • 2
  • 30
  • 47
  • With the risk of being pedantic, I want to point out that Observables CAN HOLD state. Operators like `scan` or `combineLatest`, as in the @martin comment, actually hold state. The point is that you can not get hold on such state from outside the Observable if not by means of notifications. – Picci May 21 '21 at 12:33
  • 1
    It looks like OP wants to emit array of all source values whenever any of them emits, so I think `combineLatest` is the best choice. – BizzyBob May 21 '21 at 12:58
  • 2
    @BizzyBob `combineLatest` only fires when all observables have fired at least once, Ideally I want the same behaviour but not require each observable to fire once. – synthet1c May 21 '21 at 13:15
  • @Boug I think I got it. I'm collecting the values into BehaviourSubjects, then merging the observables into a single stream, if this stream fires map the values of the cached BehaviourSubjects to the stream output. – synthet1c May 21 '21 at 13:36