3d cartoon hands holding a phone

Unlock full course by purchasing a membership

Reactive Best Friends: RxJS and Signals

STANDARD

Reactive Best Friends: RxJS and Signals

When signals were first being added to Angular, there was both tears of joy and sorrow as it seemed they were intended to replace RxJS and observables in Angular.

This never really was the intent, but they are two very similar things. If we really break it down the key point of reactive mechanisms like observables and signals is to — as you might guess — react to things.

If we have some “thing” we will call A and another thing B that is derived from A we want B to be able to react to A changing.

Sync reactivity

This is a little abstract, but we have already seen how to implement something like this with signals. The simple example often used is that of a count and doubleCount value:

count = signal(0);
doubleCount = computed(() => this.count() * 2);

In this case, count is A and doubleCount is B. If count updates, then B will react to A changing by updating its own value.

We can do the same thing with RxJS and observables and it ends up looking very similar and achieves essentially the same goal:

count$ = new BehaviorSubject(0);
doubleCount$ = this.count$.pipe(map((count) => count * 2));

NOTE: The $ syntax at the end of observable names is just a convention — it does not actually do anything.

So, it certainly might seem like signals are a replacement for observables when they seem to be tackling the same basic task (and the syntax seems a little bit easier).

In fact, signals are actually better for this scenario. Again, as we have seen, if we want to access the value of a signal we can just do this wherever and whenever we need it:

someMethod(){
  this.doubleCount();
}
<p>{{ doubleCount() }}</p>

Whereas if we want to access the value from an observable stream we need to subscribe to it:

someMethod(){
  this.doubleCount$.subscribe((value) => {
    console.log(value);
  })
}

It’s worse than this even, because to avoid memory leaks you typically want to make sure you unsubscribe from your observables when you are done with them, so it would be important to unsubscribe from doubleCount$ at some point.

To access the value in the template (without using signals) we would need to use Angular’s async pipe to pull the value out:

{{ doubleCount$ | async }}

At least in this scenario the async pipe handles the unsubscribe for us automatically.

So… why would you ever want to use observables? They just seem like more complicated versions of signals.

RxJS is hard… but worth it

There is no getting around it — RxJS and observables are a complex topic. There was almost an exact 50/50 split in the Angular community between those who loved observables and wanted more integration with observables, and those who wanted observables removed from Angular.

The big problem is that it is hard to see, and explain, how useful observables are until you see if for yourself. To see it for yourself — you need to invest the time into learning how to use it and why it makes sense.

That can be a hard justification to make when signals do seem much easier and by comparison take almost no time to learn. But, as it turns out, signals and observables are completely complimentary to each other rather than at odds with each other.

Observables and signals are suited to different tasks. The problem is that they look so similar. The key idea is that observables are great for managing events and asynchronous reactivity, and signals are great for managing state and synchronous reactivity.

I don’t expect that to mean anything to you right now (unless you have some prior knowledge in this area) — these are just concepts we are going to have to build on as we progress through the course. But, to at least give you some idea of what this means, you can consider the example we looked at before as being synchronous reactivity:

Sync reactivity

A updates and B immediately reacts by having its value update. For the searchTerm example, this assumes that the data to be filtered with the search term is available locally and can happen immediately.

This is synchronous reactivity, and this is what signals are good for.

However, we also have scenarios like this:

Async reactivity

A updates, but then some amount of time passes before B can have its value updated. An example of this is making an HTTP request to a server — this takes some amount of time to complete. We can use the same searchTerm example, but this time we know that the search can not happen immediately because it needs to wait for the HTTP request to finish. If A is a search term that is being updated we need to:

  • Send a request to the server with the A search term
  • Wait for that request to complete
  • B can now react however necessary to the value returned from the server

This is asynchronous reactivity and it is what observables are good for.

NOTE: With the introduction of the resource API, we are able to also do some asynchronous tasks well with signals as well. So, we can’t just make it a black/white scenario where we say that observables are for async tasks and signals are for sync tasks. But it is still generally a useful distinction to keep in mind.

If we combine RxJS and Signals, we have something that is:

  • Great for state
  • Great for events
  • Great for synchronous reactivity
  • Great for asynchronous reactivity

The best of both worlds.

You will see some examples of how we use RxJS and observables to deal with situations like this throughout this module.

We will learn a bit more about why signals are great for state and observables are great for events in the state management module.