Willian Soares
Willian Soares

Reputation: 320

How to properly flat this Observable array?

I need to flat the results of a Observable<Order[]>[] in a Order[].

The current way that I'm doing it:

const ordersObservable: Observable<Order[]>[] = [];
//ordersObservable is populated with a bunch of Observable<Order[]>
forkJoin(ordersObservable)
  .pipe(
    map((results) => ([] as Order[]).concat(...results))
  )
  .subscribe((orders: Order[]) => {
    this.orderService.set(orders);
  //...
  });

I've been told that I shouldn't be using pipe like that and should be using a RxJs function to handle that.

I've tried to use concatAll and mergeAll instead of the map, but that ended in calling the orderService for every item in ordersObservable, instead a single time with a flat array with all results of ordersObservable.

What am I doing wrong? And how can I flat the results of all the observables in a single array, preferably with a native RxJs solution?

Upvotes: 0

Views: 220

Answers (1)

DeborahK
DeborahK

Reputation: 60518

An Observable is meant to emit values. With an array of Observables, we need to subscribe to each element of the Observable array to emit the Order array.

I honestly think you should re-evaluate creating an array of Observables. If you want to post another question with the code that is generating that array of Observables, we could provide suggestions.

That said, I was able to get something to work. I didn't spend the time to see if there was an easier approach.

    from(this.ordersObservable)
      .pipe(
        concatAll(),
        scan((acc, value) => [...acc, ...value], [] as Order[]),
        takeLast(1)
      )
      .subscribe((x) => console.log('result', JSON.stringify(x)));

First, I needed something to subscribe to. We can't subscribe to the array of Observables. So I used from to turn the Array of Observables into another Observable. That way I could subscribe and get the code to execute.

The from emits each Observable from the array.

NOTE: forkJoin and combineLatest also work instead of from and provide the same result.

I then use concatAll() to concatenate the inner Observables in sequence. It subscribes to each Observable in the array.

scan allows us to define an accumulator, accumulating each array of Orders into a single array of orders.

UPDATE: Added takeLast(1) to ensure only the last result is emitted.

UPDATE 2: Second option

This also worked and may be a bit simpler:

    concat(...this.ordersObservable)
      .pipe(
        scan((acc, value) => [...acc, ...value], [] as Order[]),
        takeLast(1)
      )
      .subscribe((x) => console.log('result', JSON.stringify(x)));

This uses array destructuring (the "...") with the concat operator to emit all of the values from all of the Observables in the array of Observables. Then we still use scan to accumulate them and takeLast(1) to emit only once.

I would be interested to know if anyone else is able to simplify this!

Upvotes: 1

Related Questions