Best way to "flatten" an array inside an RxJS Observable

AngularRxjsRxjs5

Angular Problem Overview


My backend frequently returns data as an array inside an RxJS 5 Observable (I'm using Angular 2).

I often find myself wanting to process the array items individually with RxJS operators and I do so with the following code (JSBin):

const dataFromBackend = Rx.Observable.of([
  { name: 'item1', active: true },
  { name: 'item2', active: false },
  { name: 'item3', active: true }
]);

dataFromBackend
  // At this point, the obs emits a SINGLE array of items
  .do(items => console.log(items))
  // I flatten the array so that the obs emits each item INDIVIDUALLY
  .mergeMap(val => val)
  // At this point, the obs emits each item individually
  .do(item => console.log(item))
  // I can keep transforming each item using RxJS operators.
  // Most likely, I will project the item into another obs with mergeMap()
  .map(item => item.name)
  // When I'm done transforming the items, I gather them in a single array again
  .toArray()
  .subscribe();

The mergeMap(val => val) line doesn't feel very idiomatic.

Is there a better way to apply transformations to the members of an array that's emitted by an Observable?

NB. I want RxJS operators (vs array methods) to transform my items because I need the ability to project each item into a second observable. Typical use case: backend returns of list of item ids and I need to request all of these items from the backend.

Angular Solutions


Solution 1 - Angular

You can use concatAll() or mergeAll() without any parameter.

dataFromBackend.pipe(
  tap(items => console.log(items)),
  mergeAll(), // or concatAll()
)

This (including mergeMap) works only in RxJS 5 because it treats Observables, arrays, array-like objects, Promises, etc. the same way.

Eventually you could do also:

mergeMap(val => from(val).pipe(
  tap(item => console.log(item)),
  map(item => item.name),
)),
toArray(),

Jan 2019: Updated for RxJS 6

Solution 2 - Angular

Actually if you need it inside the stream just use this:

.flatMap(items => of(...items))

Solution 3 - Angular

Angular 6 note.

If you are using as a pipeable operator, do is known as tap!

https://www.learnrxjs.io/operators/utility/do.html Here is an example.

// RxJS 
import { tap, map, of, mergeMap} from 'rxjs/operators';

backendSource
  .pipe(
   tap(items => console.log(items)),
   mergeMap(item => item ),
   map(item => console.log(item.property))
  );

Solution 4 - Angular

If it is a synchronous operation, I would suggest to use javascript's Array.map instead, it even should save you some performance:

const dataFromBackend = Rx.Observable.of([
  { name: 'item1', active: true },
  { name: 'item2', active: false },
  { name: 'item3', active: true }
]);

dataFromBackend
  .map(items => items.map(item => item.name))
  .subscribe();

Solution 5 - Angular

I had a similar problem:

The following

  • runs on each item,
  • plucks a deeply nested object
  • converts the server response to back an array
  • consumed in template as async pipe
    this.metaData = backendPostReq(body)
      .pipe(
        mergeMap(item => item),   // splits the array into individual objects
        pluck('metaData', 'dataPie', 'data'), // plucks deeply nested property
        toArray()  // converted back to an array for ag grid
      )

    <ag-grid-angular
        *ngIf="metaData | async as result"
        [gridOptions]="dataCollectionConfig"
        [rowData]="result"
        [modules]="modules"
        class="ag-theme-balham data-collection-grid"
    >
    </ag-grid-angular>

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionAngularChefView Question on Stackoverflow
Solution 1 - AngularmartinView Answer on Stackoverflow
Solution 2 - AngularNick WaitsView Answer on Stackoverflow
Solution 3 - AngularenglishPeteView Answer on Stackoverflow
Solution 4 - AngularolsnView Answer on Stackoverflow
Solution 5 - AngularpixlboyView Answer on Stackoverflow