BACK
Lessons22. Higher-Order Observables
Lessons · 22

22. Higher-Order Observables

When a value becomes a stream

So far we've turned values into other values. But in real life it often goes further: a single value needs to become asynchronous work. For example:

  • A user id → an HTTP request for that user.
  • A click → submitting a form.
  • Search text → a request to a search API.

If we return an Observable from inside map, the type becomes Observable<Observable<T>> — a "stream of streams." That's what's called a higher-order Observable.

The problem

Subscribe to a stream like that and you get not data but another Observable. To reach the values you'd have to subscribe again — inside the subscription. It works, but it's bad: no cancellation, no real control over ordering, and leaks are easy.

Where we're headed

Next we'll learn four flattening operators that solve this in different ways: mergeMap, concatMap, switchMap, exhaustMap. Each one takes a value, creates an inner Observable, and subscribes to it automatically. The difference is how they manage ordering and cancellation.

A learning exercise

In this lesson we deliberately do it "the bad way" — a subscription inside a subscription. The point is for you to see the problem with your own eyes. We'll fix it next.

Your task

  1. In pipe, add map(id => of('User ' + id)). Notice: inside map we return an Observable, not a string.
  2. Inside the outer subscription, add inner$.subscribe(user => console.log(user)) — that's the second, nested subscription.
  3. Expected output: User 1 → User 2.
Solution spoiler · click to reveal
const { of, map } = Rx;

const userIds$ = of(1, 2);

const requests$ = userIds$.pipe(
  map(id => of('User ' + id))
);

requests$.subscribe(inner$ => {
  inner$.subscribe(user => console.log(user));
});
script.ts // TypeScript
CONSOLE · Console output
Hit Run to see the result...