Understanding concatMap

The concatMap operator is a fusion of two RxJS operators: concat and map. Through the map portion, each value emitted by the source observable is transformed into another observable stream — commonly called an inner stream. The concat portion behaves like concatAll, merging all inner observable streams generated by the mapping, and emitting values from each stream sequentially, one after another.

Whenever any of the combined sequences produce a value, that value is forwarded as part of the final output sequence. This mechanism is frequently described in the documentation as flattening.

Reach for this operator when the sequence of emissions matters — you want to prioritize seeing values from streams that arrived earlier, even if later streams produce output sooner.

Here’s how the operator functions step by step:

  1. Start by subscribing to the source observable.
  2. Each time the source emits a new value, run the map function to obtain an inner observable.
  3. Subscribe to that inner observable.
  4. Forward every emission from that inner observable down to the observer.
  5. When another value arrives from the source, run the map function again to get a fresh inner observable.
  6. Place this inner observable into a waiting queue.
  7. Only after the currently active inner observable finishes, subscribe to the next one queued up.
  8. Send the completion notification to the observer only after the source observable completes and every inner observable has also finished.
  9. If any inner observable signals an error, immediately send the error notification to the observer.

Common Scenarios

This operator is typically applied when you need to convert a plain value into an observable. A frequent use case involves translating URL strings into HTTP request observables. In contrast to mergeMap, concatMap ensures that all requests are executed one at a time — the next subscription happens only after the preceding request has fully completed.

Below is an illustration of how concatMap can be employed for this exact purpose:

const urls = [
   'https://api.mocki.io/v1/0350b5d5',
   'https://api.mocki.io/v1/ce5f60e2'
];

from(urls).pipe(
   concatMap((url) => {
       return fromFetch(url);
   })
).subscribe((response) => console.log(response.status));

Hands-On Playground

Further Reading