Your experiment setup may not be robust enough to showcase the disparities between these two operators. When an observable only emits once, there is essentially no discernible distinction between concatMap
and flatMap
(also known as mergeMap). The discrepancies become apparent when there are multiple emissions.
Let's introduce a new scenario. Let's create a source$
observable that sequentially emits increasing integers every 1 second. Then, within our "Higher Order Mapping Operator" (concatMap
& mergeMap
), we will return an observable that emits varying times every 1 second before completing.
// emit number every second
const source$ = interval(1000).pipe(map(n => n+1));
// function to generate observable emitting specified number of times
function inner$(max: number, description: string): Observable<string> {
return interval(1000).pipe(
map(n => `[${description}: inner source ${max}] ${n+1}/${max}`),
take(max),
);
}
We will then define two separate observables based on source$
and inner$
; one using concatMap
and another using flatMap
, and observe the outcomes.
const flatMap$ = source$.pipe(
flatMap(n => inner$(n, 'flatMap$'))
);
const concatMap$ = source$.pipe(
concatMap(n => inner$(n, 'concatMap$'))
);
Prior to reviewing the distinctions in output, let's address their shared characteristics. Both operators:
- subscribe to the observable returned by the provided function
- emit values from this "inner observable"
- unsubscribe from the inner observable(s)
The dissimilarity lies in how they produce and manage inner subscriptions:
concatMap
- permits only one inner subscription at any given time. It adheres to a sequential approach where it subscribes to one inner observable initially, followed by subsequent ones upon completion. This aligns with the behavior of the static method concat
.
flatMap
(or mergeMap
) - allows multiple concurrent inner subscriptions. It subscribes to inner observables as new emissions arrive, resulting in potentially unordered emissions, akin to the functionality of the static method merge
(hence my preference for the name "mergeMap").
See a demonstration of the outputs generated by the above observables concatMap$
and mergeMap$
in this StackBlitz:
https://i.sstatic.net/pNLNCl.png
I hope this explanation clarifies your doubts!
#1 - "use of flatMap should mix the outputs"
The lack of desired results here can be attributed to only a single emission passing through flatMap
, meaning there was only one "inner observable" emitting values. As exemplified earlier, having numerous emissions enables multiple independent inner observables under flatMap.
#2 - "...and include the code with multiple emitted events then things get messy."
The chaos you experienced arises from having several inner subscriptions emitting values concurrently.
In regards to utilizing concatMap
and encountering mixed output, such unpredictability is unexpected. StackBlitz sometimes exhibits odd behavior with observable emissions while auto-saving is active, potentially retaining outdated subscriptions during refreshes, hence the disorderly console output. The same could apply to Code Sandbox.
#3 - "The reason I have gone to flatMap and concatMap was to find a replacement for nested subscriptions in angular using the http library"
This rationale is sound. Nested subscriptions are best avoided due to uncertainties surrounding the cleanup of inner subscriptions.
For most scenarios involving http calls, I find switchMap
to be optimal as it discards emissions from irrelevant inner observables. Consider a situation where a component fetches data via an http call based on an id
extracted from a route parameter.
itemId$ = this.activeRoute.params.pipe(
map(params => params['id']),
distinctUntilChanged()
);
item$ = this.itemId$.pipe(
switchMap(id => http.get(`${serverUrl}/items/${id}`)),
map(response => response.data)
);
The aim with item$
is to emit the current item corresponding to the id in the URL. If the user rapidly clicks a button to navigate to a different item, causing rapid changes in the URL param ahead of data retrieval completion, opting for mergeMap
would yield multiple emissions from various http calls. This might result in screen flickering or potential display desynchronization if responses arrive out of order.
With concatMap
, users endure a serialized wait for all http calls to conclude, regardless of relevance. Conversely, switchMap
swiftly unsubscribes from previous inner observables upon receiving a new emission (new itemId
), ensuring emission solely from the latest relevant call.
Note that since http observables emit just once, the choice among operators (switchMap, mergeMap, concatMap) may seem inconsequential, given their automated handling of inner observables. Nonetheless, selecting the ideal operator now future-proofs your code to accommodate potential multiple emissions, guaranteeing anticipated behavior.