I haven't been able to find the answer to this question, but what's the difference between concat map and map? Specifically, I have an example that made it very confusing for me:
const domainsObservable = this.auth.getAuthDomains()
.shareReplay()
.concatMap((authDomains) => authDomains.map((domain) => this.toDomain(domain, connectionsObservable)))
.filter((authDomain) => this.isValidDomain(authDomain))
.toArray();
This was calling a method from a service, getAuthDomains()
that just returned an array of 3 domains. When I used the above implementation, I instead ended up with an array of 4 arrays of which each have an array of 3 domains. Somehow, it quadrupled the amount of data in the response.
I then switched over to the following implementation:
const domainsObservable = this.auth.getAuthDomains()
.shareReplay()
.map(authDomains => authDomains.map(domain => this.toDomain(domain, connectionsObservable)))
.map(authDomains => authDomains.filter(domain => this.isValidDomain(domain)));
This gave me exactly what I needed - a single array that was correctly filtered.
So why did switching over to map
dramatically change the results? Did the order of my operations matter?
Thanks
Simply said the two operators do the following:
map()
- projects each value into a different value that is propagated further down the chain:
.map(v => v * 2)
This is basically the same as Array.map()
.
concatMap()
- projects each value into an Observable and subscribes to it. The operator is always subscribed to only one inner Observable so if the source Observable emits values faster they're buffered inside concatMap()
:
.concatMap(v => Observable.of(v * 2))
...or typically in Angular you'd do something like this:
.concatMap(v => this.http.get(`whatever/${v}`))
But here comes the interesting part. In RxJS 5 operators that subscribe to inner Observables (Observables returned from projection function, etc.) in fact expect so called "observable input". This means that you can freely interchange Observables, Promises, Observables-like objects, ... and also JavaScript arrays.
Internally it's implemented using the subscribeToResult
function. Note that if you pass it an array it'll iterate it and emit each value separatelly: https://github.com/ReactiveX/rxjs/blob/master/src/internal/util/subscribeToResult.ts#L17
This means that you can use concatMap()
to "unpack" arrays into single emissions. For example:
Observable.of(42)
.concatMap(v => [1, 2, 3])
.subscribe(console.log)
This will print numbers:
1
2
3
On the other hand if you used just map
it would simply pass the array further:
Observable.of(42)
.map(v => [1, 2, 3])
.subscribe(console.log)
This will print numbers:
[1,2,3]
So concatMap
is sometimes used only to unpack an array coming from the source Observable with concatMap(array => array)
and that's exactly what's happening in your code.
Which one is better is up to you. Typically it's easier to perform simple operations like map
or filter
on the Array object itself instead of unpacking and processing in the Rx chain for performance reasons.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With