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
- In
pipe, addmap(id => of('User ' + id)). Notice: insidemapwe return an Observable, not a string. - Inside the outer subscription, add
inner$.subscribe(user => console.log(user))— that's the second, nested subscription. - Expected output:
User 1 → User 2.
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));
});