Reputation: 14077
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.
Upvotes: 65
Views: 67619
Reputation: 6670
The most idiomatic is probably .concatMap(arr => rxjs.from(arr))
Upvotes: 1
Reputation: 96891
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
Upvotes: 70
Reputation: 1502
I had a similar problem:
The following
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>
Upvotes: 0
Reputation: 847
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))
);
Upvotes: 5
Reputation: 104
Actually if you need it inside the stream just use this:
.flatMap(items => of(...items))
Upvotes: 5
Reputation: 16882
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();
Upvotes: 3