tldr;
For years now, Angular developers have been working with observables and state management libraries to make their applications reactive. Everyone has an opinion, and there have been a lot of different implementations. Recently, however, the Angular team released an official path forward for reactivity in Angular apps: Signals.
What are Signals?
According to the Angular docs, Angular Signals “is a system that granularly tracks how and where your state is used throughout an application, allowing the framework to optimize rendering updates.” In other words, it’s a way to manage state in the application in a way that allows Angular to react to changes in the most effective way possible. Effective use of signals will boost the performance of your application.
In essence, a signal is a wrapper around a value that allows consumers to be notified of changes to the value. The value inside a signal can be a simple primitive value, or a complex data structure. Because the value is always pulled from the signal via a getter, the framework always knows where the value is being used. Finally, signals can be either writable or read-only.
Signals are not unique to nor are they new with Angular. Similar implementations have been implemented in other frameworks. For example, SolidJS has been using signals for a while now, and the Angular team worked closely with the creator of Solid to figure out how best to implement signals in Angular.
Why do we need Signals?
When I first heard about the proposal for signals, I was confused as to why we needed them. Initially everyone was talking about their reactivity being one of the big draws. That was appealing, but I didn’t see the need since we already had Observables. Then I began to hear about the possibility that Signals would remove the need for using Zone.js in Angular apps in the future.
At a high level, Zone.js is a piece of the Angular framework that helps determine what parts of the app should be re-rendered and when. Zone.js and change detection are not always the most efficient of tasks. There are times where components may be re-rendered even though they don’t actually have any updates to display., Any improvements that can be made will be greatly beneficial, especially as apps grow in size.
Because Angular has a better idea of what parts of the application need updating when using Signals, updates to the application will be much more effective and efficient. As signals are adopted more in the future, the performance of Angular apps will be much better.
Creating Your First Signal
Alright, we have an idea now of what signals are and why we need them. Let’s now look into how we can create a signal and use it in an application. To create a signal, you need to import signal
from @angular/core
. Then you can create a variable like this:
public name = signal('Preston');
In this simple example, we have created a new signal and stored it in the name
variable. We gave it an initial value of “Preston”. To display the value of the signal in the component, we use interpolation like normal, but you have to add parentheses to get the value from the signal:
<p>{{ name() }}</p>
That wasn’t so bad, was it? But it will not be very often, if ever, that we create a signal and then never update the value of the signal. So how do we update it?
public updateName(newName: string) {
this.name.set(newName);
}
When this method is called, the value of the signal is updated and the value output to the screen is updated as well. In a lot of ways, this simple example is really no different from using a normal variable and re-assigning the value. But in more complex situations and at a larger scale, this method is more performant.
More Ways of Interacting with Signals
Signals are more flexible and can do more than simply setting a new value. Let’s look at some of the other things you can do with signals.
update
The update
method is another way to set a new value in the signal. The difference is that you can use the current value of the signal to derive the new value. Think about a situation where you’re keeping track of the number of times a button is clicked. Each time the button is clicked, we need to increment the value. We can do that like this:
export class ButtonIncrementComponent {
counter = signal(0);
buttonClicked() {
this.counter.update(current => current + 1;
}
}
Each time the button is clicked, the signal is updated by taking the current value and adding one to it.
mutate
The mutate
method is similar to set
or update
, in that it changes the current value of the signal, but it does so by actually changing the value in place. In other words, you could update an attribute of an object in the signal without needing to pass in the entire object again.
export class ItemDisplayComponent {
public item = signal({ title: 'Item 1', completed: false });
markAsCompleted() {
this.item.mutate(item => item.completed = true);
}
}
In this example, an arrow function is passed as the parameter to the mutate
method. This gives us access to the item
in the signal, and allows us to alter the completed attribute in place. We didn’t need to pass in a completely new object to store in the signal.
Computed Signals
Some signals are special signals called computed signals. These signals are created based on the value of a different signal. An example of this might be in a shopping cart. You may have one signal that has the cost of the items in your cart, but you may want to display the amount of tax that the person owes as well. You can use a computed signal to calculate the value of the tax owed based on the total of the items in the cart. As the total is updated, the tax will automatically be updated as well.
export class ShoppingCartComponent {
public subtotal: WritableSignal<number> = signal(0);
public tax: Signal<number> = computed(() => subtotal() * .07);
addItemToCart(amount) {
this.subtotal.update(tot => tot + amount);
}
}
In this example, our cart has a subtotal
which is a WritableSignal
that stores a number. There is also a tax
signal that has a value based on the value of the subtotal
. When we add an item to the cart, the subtotal
is updated. The tax
will automatically be updated as well, without the need of explicitly updating the value.
There are a couple interesting things to note about computed signals. The first is that they are not writable. They are read-only. Second is that the value is not computed until the first time the value which it relies upon is read. In this case, not until subtotal
is read the first time. After the value is computed for tax
the first time, its value is cached and stored for future reads. Its value will not be re-calculated again until the value of subtotal
is changed. This is done automatically for all computed signals.
Computed values are extremely powerful and beneficial. The ability to update a value on the screen automatically when we update another signal is very convenient and has many use cases. I have come across many situations similar to the example listed above where computed signals would have been very convenient. In my opinion, this is one of the biggest reasons we have to be excited about signals.
Effects
Another piece of Angular Signals that has use cases, though few and far between, are effects. Effects are operations that are run when one or more signal value changes. Effects always run at least once, and always run asynchronously.
The official Angular docs say the following about effects:
Effects are rarely needed in most application code, but may be useful in specific circumstances. Here are some examples of situations where an
[effect](https://angular.io/api/core/effect)
might be a good solution:
- Logging data being displayed and when it changes, either for analytics or as a debugging tool
- Keeping data in sync with
window.localStorage
- Adding custom DOM behavior that can't be expressed with template syntax
- Performing custom rendering to a
<canvas>
, charting library, or other third party UI library
Effects can be useful, but should be used with care. All the performance benefits of signals would be lost if you got yourself in an infinite loop.
Conclusion
Angular is moving forward at a rapid pace. Signals is just the latest example of that progress. The performance benefits and reactivity of signals is bound to permeate the community and apps in the coming months. While signals are still in developer preview, this is a good time to familiarize yourself with them and find use cases for them. In a future release, likely within the year (based on other features that went into developer preview), signals will exit developer preview and be ready to go officially.