Before going into the topic let's brief …How Angular currently deals with reactive state management.
we all know that In Angular, the primary way to manage state changes is through services, components, and data binding using properties and events or RxJs. Angular provides features like two-way data binding, event binding, and property binding to facilitate the flow of data between components and their templates.
On the other hand, Solid.js is a separate JavaScript library that introduces the concept of "Signals" for managing reactive state in web applications. It is not directly related to Angular. But now in Angular 16 they introduced the concept of signal (Still under developer preview) included in @angular/core.
What is this Signal actually… ?
Signals are a new way of managing state changes in Angular applications, inspired by Solid.js. Signals are functions that return a value (get())and can be updated by calling them with a new value (set()). Signals can also depend on other signals, creating a reactive value graph that automatically updates when any dependencies change. Signals can be used with RxJS observables, which are still supported in Angular v16, to create powerful and declarative data flows.
Signals offer several advantages over the traditional change detection mechanism in Angular, which relies on Zone.js to monkey-patch browser APIs and trigger change detection globally. Signals allow you to run change detection only in affected components, without traversing the entire component tree or using Zone.js. This improves runtime performance and reduces complexity.
I know this theory is boring. Let's dive into the action.
Let's say you have an e-commerce application where users can add items to their shopping cart. You want to display the total price of the items and update it every time a new item is added or removed. Here's how you can use Signals to achieve this:
@Component({
selector: 'my-cart',
template: `
<ul>
<li *ngFor="let item of items">
{{item.name}} - ${{item.price}}
<button (click)="removeItem(item)">Remove</button>
</li>
</ul>
Total Price: ${{totalPrice()}}
`,
})
export class CartComponent {
items = [ { name: 'Product A', price: 10 }, { name: 'Product B', price: 15 }, { name: 'Product C', price: 20 }, ];
// Define a signal for the list of items
itemList = signal(this.items);
// Define a computed value for the total price
totalPrice = computed(() => {
return this.itemList().reduce((acc, curr) => acc + curr.price, 0);
});
removeItem(item) {
// Update the itemList signal by removing the selected item
this.itemList.set(this.itemList().filter((i) => i !== item));
}
}
In this example, we define a signal itemList for the list of items in the cart and a computed value totalPrice that depends on itemList. Whenever an item is removed from the cart, we update the itemList signal which triggers the re-calculation of totalPrice.
using Computed() we can recalculate several other signal values if some signal changes.
If there is any change in signal that notifies all the dependant who listens to it.
`// To set a value of a signal you call the function signal like
variableName = signal(value);
//And to get the value out of the signal you call the variableName as a function like
{{ variableName() }}
`To change the value of a signal there are 2 methods available. Update and mutate.
variableName = signal<any>({
a: 'Test'
});
variableName.mutate(
currentValue => currentValue.b = 'Another Test';
);
//or
variableName = signal<CustomerItem[]>([
{
customer: 'Bhanu'
}
]);
variableName.mutate(
customerArray => customerArray.push = {
customer: 'Prakash'
};
);
Effect
Effects are a concept that developers might already know from NGRX. Besides just getting the value by calling the signal by name like variableName(), they trigger an action that has no impact on the signals value but is executed when the value of the signal changes. The effect is also triggered when the function is executed for the first time. This means you can not only define it and it will be triggered once the signals value changes. Its executed the first time the code runs over the effect.
public weatherData$: Observable<WeatherData>;
public dateSignal = signal<Date>(Date.now());
public effectRef = effect(
() => {
this.weatherData$ = this.httpClient.get<WeatherData>('/weatherData?date=' + dateSignal());
}
)
If the effect had an if else statement in it where one signal was in the if and another signal was in the else statement. It will register to both signals. But it is only executed when one of those 2 signals change. It's not executed when a third signal changes that is not mentioned in the effect.
The effect() function returns an Effect. It can be manually scheduled or destroyed. There are 3 methods on the Effect.
schedule(): Schedule the effect for manual execution.
destroy(): Shut down the effect, removing it from any upcoming scheduled executions.
consumer: Direct access to the effect's Consumer for advanced use cases.
computed
The computed function is like an effect. The difference is that it returns a new (and immutable) signal of type Signal instead of an Effect. This means that we can recalculate several other signal values if some signal changes.
import { Component, computed, SettableSignal, signal, Signal } from '@angular/core';
@Component({
selector: 'signal-test-component',
template: '{{valueX()}}'
})
export class SignalTestComponent {
public valueX: SettableSignal<number> = signal<number>(5);
public valueY: SettableSignal<number> = signal<number>(5);
public valueZ: Signal<number>;
constructor() {
console.log('The number is: ' + valueX());
this.valueZ = computed(
() => this.valueX() + this.valueY()
);
}
}
valueZ will always depend on valueX and valueY and therefore has no methods to update or mutate. It does not need them. That's the difference between a SettableSignal and a Signal.
Nice visual explained by TeckShareSkk
Sample E-Comm App using Angular 16 Signals.
I hope you find this post helpful. Thanks for reading.
Top comments (0)