#angular #rxjs #advanced #operators

Day 14 — Advanced RxJS: switchMap, mergeMap, forkJoin & CatchError

📘 Day 14 — Advanced RxJS: switchMap, mergeMap, forkJoin & CatchError

Zero to Hero — Hands-on Angular Tutorial

Today you will learn:

  • ✔️ The concept of Higher-Order Mapping (Flattening)
  • ✔️ switchMap (The cancel & switch operator)
  • ✔️ mergeMap (The parallel/fire-hose operator)
  • ✔️ concatMap (The queue/serial operator)
  • ✔️ forkJoin (Waiting for multiple APIs)
  • ✔️ catchError (Handling stream failures)

This is the hardest part of RxJS. Once you understand this, you are in the top 10% of Angular developers. 🧠


🟦 1. The Problem: Nested Subscriptions (Callback Hell)

Imagine you need to:

  1. Get a User ID.
  2. Use that ID to fetch User Details.

Bad Way (Nested Subscriptions):

this.route.params.subscribe(params => {
  const id = params['id'];
  
  // ⚠️ TRAP: Nested Subscribe! Hard to unsubscribe, leaky.
  this.http.get(`/api/users/${id}`).subscribe(user => {
    console.log(user);
  });
});

Good Way (Flattening Operators): Used to “map” one Observable into another.


🟩 2. switchMap (The “Latest & Greatest”)

Behavior: When a new value arrives, cancel the previous inner observable and start the new one.

Use Case: Typeahead Search. If I type “Ang”, request starts. If I quickly type “Angu”, cancel “Ang” request (save bandwidth) and search “Angu”.

import { switchMap } from 'rxjs/operators';

searchField.valueChanges.pipe(
  debounceTime(300),
  // If user types again, CANCEL previous HTTP call
  switchMap(term => this.http.get(`/api/search?q=${term}`))
).subscribe(results => {
  console.log('Search results:', results);
});

🟧 3. mergeMap (The “Fire Hose”)

Behavior: Run everything in parallel. Don’t cancel anything.

Use Case: Deleting multiple items. If I click “Delete” on 5 items quickly, I want ALL 5 delete requests to go to the server. Do not cancel any.

import { mergeMap } from 'rxjs/operators';
import { from } from 'rxjs';

const idsToDelete = [1, 2, 3];

from(idsToDelete).pipe(
  // Send all requests at once!
  mergeMap(id => this.http.delete(`/api/users/${id}`))
).subscribe(res => console.log('Deleted one item'));

🟥 4. concatMap (The “Queue”)

Behavior: Wait for the current one to finish before starting the next. (Sequential).

Use Case: Saving data where order matters. (e.g., Step 1 must finish before Step 2 starts).

import { concatMap } from 'rxjs/operators';

// Imagine saving a form in precise steps
const steps$ = from(['Step 1', 'Step 2', 'Step 3']);

steps$.pipe(
  concatMap(step => this.saveStepApi(step))
).subscribe(res => console.log('Step completed in order!'));

🟫 5. forkJoin (Promise.all for Observables)

Behavior: Wait for ALL provided observables to complete, then give me the final results as an array.

Use Case: Loading a Dashboard. You need Profile, Notifications, and Settings before showing the page.

import { forkJoin } from 'rxjs';

forkJoin({
  profile: this.http.get('/api/profile'),
  notifications: this.http.get('/api/notifications'),
  settings: this.http.get('/api/settings')
}).subscribe({
  next: (result) => {
    console.log(result.profile);
    console.log(result.notifications);
    console.log(result.settings);
    // Render UI now...
  }
});

🟨 6. Robust Error Handling

If an Observable errors, it dies (stops listening). To keep it alive (e.g., in a Search bar), catch the error inside the pipe.

import { catchError, of, EMPTY } from 'rxjs';

source$.pipe(
  switchMap(term => 
    this.http.get(url).pipe(
      // 🛡️ Catch error HERE (Inner Observable)
      // Return 'EMPTY' (ignore) or fallback data 'of([])'
      catchError(err => {
        console.error('API Failed', err);
        return of([]); // Return empty results to keep app running
      })
    )
  )
).subscribe(); // The main subscription stays alive!

🎉 End of Day 14 — What You Learned

Today you mastered the “Big 4” of RxJS/Angular Mapping:

  • ✔️ switchMap: Cancel previous (Search).
  • ✔️ mergeMap: Parallel execution (Deletes/Writes).
  • ✔️ concatMap: Sequential execution (Order sensitive).
  • ✔️ forkJoin: Join multiple sources (Initial Load).
  • ✔️ catchError: Prevent crashes.

You are now capable of handling complex async logic that Signals alone cannot easily do.


🧪 Day 14 Challenge

Build a “Dashboard Loader” Service.

Requirements:

  1. Mock 3 API calls (Users, Posts, Comments) using timer() or of().pipe(delay()).
  2. Use forkJoin to wait for all 3.
  3. Display “Loading…” while waiting.
  4. Display the combined data when finished.
  5. Bonus: Make one API fail, and handle it gracefully using catchError so the other 2 still show up.