user1027620
user1027620

Reputation: 2785

Mapping values from combineLatest to Array

Having this:

 Observable.combineLatest(localArray.map((i) => {
  return <Observable that returns $value> }))
  .map((statuses) => {
    return statuses.filter((status) => {
      return status.$value === CONDITION
    });
  }).subscribe((eligible: any[]) => {
...

Is it possible to map the results from the subscription back to the localArray? I'd like to know which eligible item belongs to which localArray entry...

Thanks for the help.

I read somewhere that the order is preserved with combineLatest. However, mapping directly to the index with a forEach will not work since the eligible results might return different length than the localArray if the condition(s) are met.

I can always remove .map() and do the filtering in the subscribe block which will enable me to loop over the localArray and update with the eligible data directly as such: localArray.forEach((ai, i) => { ai.eligible = eligible[i] }) for example...

Upvotes: 2

Views: 3533

Answers (2)

Jon Welker
Jon Welker

Reputation: 178

First, the code:

Observable
    // Turn an array, promise, or iterable into an observable.
    .from(localArray)
    // Map values to inner observable, subscribe and emit in order.
    .concatMap((arrayElement: any) => {
        // Returning an observable that returns a value
        return Observable.of(arrayElement);
    })
    // Emit only values that meet the provided condition.
    .filter((value: any) => {
        return value === 'Some condition';
    })
    // Reduces the values from source observable to a single value that's emitted when the source completes.
    // If you need the current accumulated value on each emission, try scan - https://www.learnrxjs.io/operators/transformation/scan.html
    // The second argument here is the seed, the initial value provided the first time reduce is called. (In this case, an empty array)
    .reduce((filteredValue: any, accumulatedFilteredValues: any[]) =>{
        accumulatedFilteredValues.push(filteredValue);
        return accumulatedFilteredValues;
    }, [])
    .subscribe((filteredValueArray: any[]) => {
        // Do something with the array of ordered, filtered values
    });

First, I created an observable to emit each arrayElement in localArray using from.

When each arrayElement is emitted, I map that arrayElement to an Observable that will return some value (in this case I just return the arrayElement). I'm using concatMap because it will subscribe to the inner observable and emit their value once they complete, in the order they are emitted.

I use filter to only emit a value that satisfies a condition. Filter takes a function that returns either true or false. If true is returned, the value will be emitted.

Finally, I use reduce to collect the filteredValue and add it to an array of accumulatedFilteredValues. Reduce takes a function as its first argument, and an optional seed as its second.

The function passed to reduce receives the most recent emission (filteredValue) as its first argument and an accumulated value as the second (the empty array [])

After applying these operators to the emitted values, the function passed to subscribe receives an array containing the filtered, ordered items from localArray.

learn-rxjs is an excellent RxJS resource with examples and descriptions of various operators.

If you're using RxJS >= 5.5, consider using the pipeable operators instead. In that case, the code would look a little closer to this:

from(localArray).pipe(
        concatMap((arrayElement: any) => {
            return Observable.of(arrayElement);
        }),
        filter(value => {
            return value === 'Some condition';
        }),
        reduce((filteredValue: any, accumulatedFilteredValues: any[]) => {
            accumulatedFilteredValues.push(filteredValue);
            return accumulatedFilteredValues;
        }, [])
    )
    .subscribe((filteredValueArray: any[]) => {
        // Do something with the array of ordered, filtered values
    });

Upvotes: 1

Lodewijk Bogaards
Lodewijk Bogaards

Reputation: 19987

It is really hard to judge with the little amount of information that you have given whether that is a good idea or not, but it certainly doesn't sound like it to me. The reason I say that is because mapping back to the localArray would create an implicit loop in your data stream. Perhaps that is exactly what you need, so that is up to you.

What you can do in this situation is simply also emit the index (or some other identifier) that helps you map back to localArray from the combineLatest.

Something like this.

Observable.combineLatest(localArray.map((i) => {
  return <Observable that returns $value> }))
  .map((statuses) => {
    return [i, statuses.filter((status) => {
      return status.$value === CONDITION
    })];
  }).subscribe((eligible: [number, any[]]) => {
    // eligible now is a tuple type where the left element is the index of the localArray
  });

Upvotes: 1

Related Questions