Hot and Cold Observables
Tags: angular, rxjs, observables
Published: 2023-02-26 19:26:17

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. Every time our listener clicks “play”, our player creates a playlist and starts playing it.

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:

* You should not expect that events will be emitted only after your `subscribe()` call. It is quite an important thing to grasp;
* 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;
* 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:

* 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;
* You can expect that the response will be emitted after `subscribe()` so you will not miss it;
* 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:

```ts
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:

```ts
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 just `share()` — 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()`:

```ts
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:

```ts
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()`.


_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._

---
💙 _If you enjoy my articles, consider [following me on Twitter](https://twitter.com/eugeniyoz), and/or [subscribing](https://medium.com/@eugeniyoz) to receive my new articles by email._  
🎩️ _If you or your company is looking for an Angular consultant, you can [purchase my consultations on Upwork](https://www.upwork.com/services/consultation/development-it-evgeniy-1696285343741362176?ref=project_share)._