Angular Signals in a Nutshell

Everything you need to know about the upcoming reactive primitive in Angular — also known as Angular signals.

It’s been a month since the RFC for Angular Reactivity with Signals has been announced and the hype holds well (and for good reason).

If you are on X (former Twitter), you know it! (hint: 🚦)

But the concept of “signals” isn’t something new — Solid and Vue already have some version of this mechanism.

The Angular team started working towards adding signals in Angular in the form of a reactive primitive.

This article aims to answer questions like:

  • what are signals?

  • why are they introduced in Angular?

  • what benefits do they bring?

  • how do we use them?

Let’s get started!

What are signals?

Signals! Signals everywhere! 🚦🚦🚦

But what are signals?

Well, think of them as a reactive mechanism that we can use to create reactive values for consumers to read. A signal will notify all consumers when its value changes.

Unlike observables that emit their values (either synchronously or asynchronously), signals don’t emit anything. Hence, they don’t require a subscription.

Signals are reactive and synchronous and always have an initial value. You can access only their latest value directly through a getter (e.g. value()).

See where this is going?

If you thought change detection, you are on the right track!

Why signals in Angular?

Signals will be used as a change detection mechanism in Angular.

But Angular already has a change detection mechanism — Zone.js! Why the need for a new one?

The main reason is performance!

If you are not familiar with Zone.js, you can read about it and how Angular currently relies on it for change detection in this article.

Go on, I’ll wait!

Ok, welcome back!

The bigger the application, the more likely it is for Zone.js to introduce a bottleneck to its performance.

The problem is in the dirty checking process. Zone.js can’t tell Angular which component exactly needs to be updated. It can only tell that one or more components changed in the tree.

Angular has to then run a change detection cycle for components in the tree from top to bottom —  starting from the root component.

Below is a small example of what the tree looks like.

Some event (⚡️) triggers a change in a leaf component (say ProductItem). The default strategy is to run change detection for all components in the tree (marked with red color in the image below).

Of course, we can use the OnPush strategy to reduce the number of components on which Angular has to run change detection.

This may seem like a small improvement, but trust me — it’s not! In real applications, the tree is never as shallow and small as in this example.

This is far from ideal. Wouldn’t it be nice if the component could somehow notify Angular that something has changed?

Enter Angular signals!

Change detection will run only on the component in which the change happened.

How do we use them?

Ok, enough with the theory — let’s build something!

We will build a simple VAT calculator.

The user types a price and the application calculates the VAT (fixed 15%) and the total (price + vat).

The user can click the “Save” button to save current results as a history entry. All data are saved in the local storage so the application doesn’t start blank after refreshing the page.

Here is what the application looks like.

Let’s see how we can build this with Angular’s signal functions, such as signal, computed, update, and effect.

We create a signal for the price and history with the signal function. For the VAT and total, we create a computed signal with the computed function.

The computed function receives a callback. This callback will be executed every time the value of any signal in the callback changes.

This is like declaring that VAT and total are dependent on price. Each time the price changes, the value of the VAT and total will be recalculated to be up-to-date with the latest change.

We use event binding in the template to call onInputUpdate when the user types something.

In the onInputUpdate(), we set the new value to the price signal.

Because VAT and total are computed signals, they are recalculated as soon as the value of the price changes.

The user can also save the results by pressing the “Save” button.

In the save(), we use the update function to update the signal in an immutable way — this will notify dependents of the signal.

Observe that accessing the value of a signal (lines 4–6) is done differently compared to common variables. It’s basically a function call.

Respectively, there is a mutate function to change the value of a signal in place - this will also notify dependents of the signal.

Note, however, that all three methods (set, update, mutate) notify dependents and according to the official RFC:

While the API surface has 3 different methods (set, update, mutate) of changing signal’s value, the .set(newValue) is the only fundamental operation that we need in the library. The other 2 methods are just syntactic sugar, convenience methods that could be expressed as .set.

UPDATE: Starting from Angular v17, the mutate function has been dropped from the signal API.

Finally, there is the effect function to handle side effects. We use this function to store the current price value the user typed and also keep the saved results in the local storage.

The effect function receives a callback. This callback will be executed every time the value of any signal in the callback changes.

You can find the complete source code in this GitHub repository.

Conclusions

Angular signals sure look promising!

They will provide a way for fine-grained reactivity, leading to more efficient change detection and, as a result, more performant applications.

Additionally, this is the first step towards Angular not relying on Zone.js for change detection.

Who knows? Maybe in the near future, we’ll see completely zone-less Angular applications.

Thank you for reading!