Mastering Angular: Hot and Cold Observables

🪄 OZ 🎩
5 min readFeb 26, 2023
“A Walk at Twilight”, Vincent van Gogh, 1889–1890

The author of this article is currently seeking job opportunities! Feel free to contact me through LinkedIn, Upwork, Twitter, or my personal website.

Observables are sources of information produced over time.
Information here can be called “events”, “values”, or just “emissions”.

Let’s take 2 things from the real world as examples of observables:

  1. Radio station, broadcasting music. No ads, just songs (yeah, it’s just an imaginary example);
  2. Pocket music player. It has only one output — for the headphones. Every time we press “play”, this player will create a new playlist and will start playing songs from that playlist.

In both examples, to press the “play” button, we should call subscribe() method.

A “radio station” is a “hot” observable ☀️

Listeners of the radio station can not know how many songs were played before they started listening.
They can not know how many songs will be played after they call unsubscribe() .
What they are interested in is listening to some next songs — after that, they will turn the radio off.

A radio station will not react to every new listener — the playlist will not be paused or re-started when a new listener turns their radio on or off.

Our pocket music player is a “cold” observable ❄️

It has, by default, only one listener.

The listener knows for sure, that the first song will be played after subscribe() call, and that there will be no songs played after unsubscribe() (“stop” button).

Now let’s use these examples with Angular.

In Angular, every @Output() is a hot observable ☀️

What it means in practice:

  1. You should not expect that events will be emitted only after your subscribe() call. It is quite an important thing to grasp;
  2. When you call subscribe(), “playlist” will not be re-generated for you. A new process will not be created for every subscriber. It is important for performance — you can call subcribe() as many times as you want and keep as many listeners as you want;
  3. When you unsubscribe(), it will not stop the emitter. The component will keep emitting new values after that.

As you can see, it’s exactly what is expected from @Outputs() — a component can not predict how many observers it will have, and it should not affect the performance.

Another source of hot observables in Angular is Router.

Are there cold observables in Angular out of the box? Sure:

Every HTTP request in Angular is a cold observable ❄️

What does it mean in practice:

  1. Every time you subscribe() (including usage of “async” pipe), a new request will be created and executed. It is a very important thing to understand and remember. It is the biggest source of performance issues and side-effect bugs with the cold observables;
  2. You can expect that the response will be emitted after subscribe() so you will not miss it;
  3. If you call unsubscribe(), no future responses will be expected, even if the first response was not received yet — the request will be canceled.

You can create a cold observable in your code.

If you have a function that creates and returns an observable — it is a cold observable.

Please remember this rule. Sometimes, a cold observable is exactly what you need, but you should be 100% sure about that to avoid bugs and performance issues.

If some function creates an observable only once or returns an observable, created outside of this function — it should be a hot observable (Subject, ReplaySubject, BehaviorSubject).

When you use async pipe in your template, you should provide only hot observables to this pipe — because you can not guarantee that this part of the template will be generated only once (especially if it’s inside structural directives such as ngIf or ngFor).

Cold observables can be converted into hot observables.
One good example of such conversion is caching the HTTP requests.

Let’s say you have getUsers() method in your ApiService, that returns the list of users and generates some very important and heavy report:

getUsers(): Observable<User[]> {
return this.http.get<User[]>('/api/users').pipe(
tap((users) => this.createPDFReport(users))
);
}

For simplicity, let’s omit cache invalidation in this example.

If we will share the resulting observable, then every subscriber will cause a new HTTP request and will call the PDF report generator — it’s not what we want.

To be able to safely share this, we should add:

getUsers(): Observable<User[]> {
return this.http.get<User[]>('/api/users').pipe(
tap((users) => this.createPDFReport(users)),
// 👇
shareReplay({bufferSize: 1, refCount: false})
);
}

We have to use shareReplay() and not justshare() — otherwise, new subscribers will not get the response if it was received before they subscribed.

bufferSize: 1 here defines how many previous values should be emitted to a new subscriber (if there are any previous values). We want to cache only the latest response, so we use value 1.

refCount: false here means that the request will not be re-created if at some moment count of subscribers dropped to zero.

You might want to add shareReplay() to some of your cold observables, created not only from HTTP calls.

Sometimes, you still need some code (“side effect”) to be executed on every subscription. You can put it after shareReplay():

getUsers(): Observable<User[]> {
return this.http.get<User[]>('/api/users').pipe(
tap((users) => this.createPDFReport(users)),
shareReplay({bufferSize: 1, refCount: false}),
// 👇
tap((users) => this.someExample(users))
);
}

If your subscribers should receive values, created only after the moment they subscribed (form inputs modifications, for example), then use share() instead of shareReplay().

Let’s use the rule I’ve mentioned before:

If you have a function that creates and returns an observable — it is a cold observable

Our getUsers() still creates and returns an observable. We are not reusing once created hot observable. Let’s fix it:

private usersCached$?: Observable<User[]>;

getUsersCached(): Observable<User[]> {
if (this.usersCached$) {
return this.usersCached$;
}
this.usersCached$ = this.getUsers();
return this.usersCached$;
}

Now getUsersCached() returns a hot observable, because that observable was created outside of the function, and it uses shareReplay() to don’t restart the producer.

From this article you’ve learned what are cold and hot observables, what are the differences, why cold observables are dangerous to share in Angular, and how you can convert them to hot observables.

🪽 Do you like this article? Share it and let it fly! 🛸

💙 If you enjoy my articles, consider following me on Twitter, and/or subscribing to receive my new articles by email.

--

--