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