Understanding RxJS Operators in Angular: Beyond switchMap

Date: June 10, 2022


Introduction

RxJS is a powerful reactive programming library, and Angular heavily relies on it for handling asynchronous data streams. While switchMap is the most popular operator for flattening observables, there are other equally important operators like exhaustMap, mergeMap, and concatMap that can solve specific use cases elegantly.

In this post, we’ll dive into these operators, explore their differences, common pitfalls, and practical Angular patterns to help you choose the right one for your scenario.


Quick Recap: What is switchMap?

switchMap maps to a new observable and cancels the previous one if it hasn’t completed yet.

Example:

searchTerm$.pipe(
  switchMap(term => this.api.search(term))
).subscribe(results => {
  this.results = results;
});

Good for user input scenarios where you want to discard previous requests when new ones come in (e.g., live search).


1. exhaustMap: Ignoring New Inner Observables While One is Active

exhaustMap ignores new emissions while the current inner observable is still running.

Use case: Prevent duplicate HTTP requests on button clicks

Example:

saveClicks$.pipe(
  exhaustMap(() => this.api.saveData())
).subscribe(() => {
  console.log('Save complete');
});

Here, if the user clicks the save button multiple times quickly, only the first click triggers the save API call until it completes; subsequent clicks are ignored until then.

Pitfall: Be careful not to block new actions forever if the inner observable never completes.


2. mergeMap: Running All Inner Observables Concurrently

mergeMap subscribes to all inner observables and merges their outputs concurrently.

Use case: Parallel API calls

Example:

userIds$.pipe(
  mergeMap(id => this.api.getUser(id))
).subscribe(user => {
  console.log('Fetched user:', user);
});

This will trigger all user fetches concurrently, and the results arrive as they complete.

Pitfall: Too many concurrent inner subscriptions can overwhelm resources. Use mergeMap’s concurrency parameter to limit parallelism:

mergeMap(id => this.api.getUser(id), 3) // Max 3 concurrent requests

3. concatMap: Queuing Inner Observables Sequentially

concatMap queues the inner observables and subscribes to them one after another, waiting for each to complete before starting the next.

Use case: Ensuring ordered execution

Example:

clicks$.pipe(
  concatMap(() => this.api.saveData())
).subscribe(() => {
  console.log('Data saved sequentially');
});

This ensures that save requests do not overlap and happen in the exact order of clicks.


Choosing the Right Operator

OperatorBehaviorCommon Use Case
switchMapCancels previous inner observableAutocomplete, live search
exhaustMapIgnores new emissions while busyPrevent duplicate form submissions
mergeMapRuns all inner observables concurrentlyParallel API calls
concatMapQueues inner observables, runs sequentiallyOrdered execution, chained requests

Real-World Angular Pattern Example

Imagine a form where users can save drafts multiple times but want to avoid overlapping saves:

saveClicks$ = new Subject<void>();

constructor(private api: ApiService) {
  this.saveClicks$.pipe(
    exhaustMap(() => this.api.saveDraft())
  ).subscribe(() => {
    this.snackBar.open('Draft saved');
  });
}

// Bind button to call this.saveClicks$.next()

This prevents double saves if the user clicks too fast.


Summary

  • switchMap: Use when you want to cancel previous async work.
  • exhaustMap: Use when you want to ignore new triggers until current finishes.
  • mergeMap: Use when you want to run all concurrently.
  • concatMap: Use when you want to queue and run sequentially.

Conclusion

Mastering these RxJS operators gives you fine-grained control over asynchronous flows in Angular apps. Experiment with each in your projects to understand which fits your scenarios best.


Comments

No comments yet. Why don’t you start the discussion?

Leave a Reply

Your email address will not be published. Required fields are marked *