Rxjs: get latest values from multiple observables later after they emitted

Viewed 2991

I have multiple observables that emit values during the lifetime of the page. For example:

chartData$: Observable;
tableData$: Observable;
filterData$: Observable;

At any time user can click the 'Download' button, and get the JSON combining the values that were last emitted from each of these observables:

downloadButtonClicked$.pipe(
    combine chartData$, tableData$ and filterData$    // <- how do I get latest values here?
).subscribe(([chart, table, filter]) => downloadJson(chart, table, filter))

but the downloadJson function shouldn't be called when any of these 3 observables emit values as part of page lifecycle, only on Download click.

TLDR;

working most elegant solution (as suggested by Mike) https://stackblitz.com/edit/typescript-jm3zma?file=index.ts

4 Answers

If you want to get the last value from each of them before every single source Observable completes you can use forkJoin() that will emit only once after all of them complete.

If you want to emit on every emission from each source Observable you can use combineLatest.

Edit:

You can chain each source Observable with shareReplay(1) so when you subscribe to them you'll get the latest value.

const chartDataShared$ = chartData$.pipe(shareReplay(1));
tableDataShared$ = ...;
filterDataShared$ = ...;

combineLatest([chartDataShared$, tableDataShared, filterDataShared$]).pipe(
  take(1)
);

This way you'll get the latest values emitted from each one of them. However, this means that in other parts of you code you'll need to use eg. chartDataShared$ instead of chartData$.

You can do it like this:

combineLatest([chartData$, tableData$, filterData$]).pipe(
  switchMap(result => downloadButtonClicked$.pipe(map(() => result)))
).subscribe(([chart, table, filter]) => {
  downloadJson(chart, table, filter);
});

Whenever chartData$, tableData$, or filterData$ emits a new value it will create a new inner subscription to downloadButtonClicked$ passing down the new data.

Note: the subscription will not be created until all of chartData$, tableData$, and filterData$ have emitted their first value. If you need the download to still trigger in this case then you can use defaultIfEmpty.

You can simply use withLatestFrom.

downloadButtonClicked$.pipe(
  withLatestFrom(chartData$, tableData$, filterData$)
).subscribe(([_, chart, table, filter]) => downloadJson(chart, table, filter))

The observable will only emit when downloadButtonClicked$ emits and combines its value with the latest values from chartData$, tableData$ and filterData$.

combineLastest will emit once all the observables have emitted. You can use switchMap to switch from downloadButtonClicked$.

downloadButtonClicked$.pipe(
   switchMap(
     () => combineLatest([chartData$, tableData$, filterData$])
   )
).subscribe(([chart, table, filter]) => downloadJson(chart, table, filter));
Related