Angular Signals

Published on May 18, 2023

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.