Home >Web Front-end >JS Tutorial >The next improvement in Angular reactivity
Since the latest versions of Angular, a new primitive reactivity system has been developed within the framework: signals!
Today, with hindsight, we realize that certain use cases had not been covered, and obviously the Angular team being very reactive will provide us with helpers to cover these uses cases.
What are these uses cases? What solutions are going to be put in place, and how are they going to be used?
Let's start by illustrating this problem.
Let's imagine we have a basket of fruit with a certain quantity.
The quantity is managed by a component, which inputs the fruit.
@Component({ template: `<button type="button" (click)="updateQuantity()"> {{quantity()}} </button>` }) export class QuantityComponent() { fruit = input.required<string>(); count = signal(1); updateQuantity(): void { this.count.update(prevCount => prevCount++); } }
Here the variable must be reset if the input price fruit changes.
A simple solution would be to use an effect
@Component({ template: `<button type="button" (click)="updateQuantity()"> {{quantity()}} </button>` }) export class QuantityComponent() { fruit = input.required<string>(); quantity = signal(1); countEffect(() => { this.fruit(); this.quantity.set(1); }, { allowSignalWrites: true }) updateQuantity(): void { this.quantity.update(prevCount => prevCount++); } }
The preceding code is bad practice. Why is this the big question?
We need to set the signalWrites option to true in order to set the signal quantity. This is due to a misinterpretation of the given problem.
In our case, we want to synchronize two variables which, in our materialization, are desynchronized
The counter is not independent of the fruit, which is our initial source. In reality, here we have a component state, whose initial source is the fruit and the rest is a derivative of the fruit.
Materialize the problem as follows
@Component({ template: `<button type="button" (click)="updateQuantity()"> {{fruitState().quantity()}} </button>` }) export class QuantityComponent() { fruit = input.required<string>(); fruitState = computed(() => ({ source: fruit(), quantity: signal(1), })); updateQuantity(): void { this.fruitState().quantity.update(prevCount => prevCount++); } }
This materialization strongly links the fruit to its quantity.
So as soon as the fruit changes, the computed variable fruitState is automatically recalculated. This recalculation returns an object with the quantity property, which is a signal initialized to 1.
By returning a signal, the variable can be incremented on click and simply reset when the fruit changes.
It's a relatively simple pattern to set up, but can't we simplify it?
With the arrival of Angular 19, a new function for calculating derived signals.
Until now, we had the computed function, but this function returns a Signal and not a WrittableSignal, which would have been practical in our previous use case for the quantity variable.
This is where LinkedSignal comes in. LinkedSignal, as its name suggests, allows you to strongly link two signals together.
If we return to our previous case, this function would allow us to simplify the code as follows:
@Component({ template: `<button type="button" (click)="updateQuantity()"> {{quantity()}} </button>` }) export class QuantityComponent() { fruit = input.required<string>(); quantity = linkedSignal({ source: fruit, computation: () => 1 }); updateQuantity(): void { this.quantity.update(prevCount => prevCount++); } }
The linkedSignal function is defined as follows:
linkedSignal(computation: () => D, options?: { equal?: ValueEqualityFn<NoInfer<D>>; }): WritableSignal<D>; linkedSignal(options: { source: () => S; computation: (source: NoInfer<S>, previous?: { source: NoInfer<S>; value: NoInfer<D>; }) => D; equal?: ValueEqualityFn<NoInfer<D>>; }): WritableSignal<D>;
In the first definition, the “abbreviated” definition, the linkedSignal function takes a computation function as a parameter and a config object.
@Component({ template: `<button type="button" (click)="updateQuantity()"> {{quantity()}} </button>` }) export class QuantityComponent() { fruit = input.required<string>(); count = signal(1); updateQuantity(): void { this.count.update(prevCount => prevCount++); } }
In this previous exemple, because the computation function depends of the quantity sigal, when the quantity change, the computation function is reevaluated.
In the second definition, the linkedFunction method takes an object as a parameter with three properties
Contrary to the “abbreviated” computation function, here the computation function takes as parameters the value of the source and a “precedent”.
@Component({ template: `<button type="button" (click)="updateQuantity()"> {{quantity()}} </button>` }) export class QuantityComponent() { fruit = input.required<string>(); quantity = signal(1); countEffect(() => { this.fruit(); this.quantity.set(1); }, { allowSignalWrites: true }) updateQuantity(): void { this.quantity.update(prevCount => prevCount++); } }
Angular 19 will introduce a new API for simple data fetching and retrieval of query status (pending etc), data and errors.
For those who are a little familiar with the framework, this new API works a bit like the useRessource hook.
Let's take a look at an example:
@Component({ template: `<button type="button" (click)="updateQuantity()"> {{fruitState().quantity()}} </button>` }) export class QuantityComponent() { fruit = input.required<string>(); fruitState = computed(() => ({ source: fruit(), quantity: signal(1), })); updateQuantity(): void { this.fruitState().quantity.update(prevCount => prevCount++); } }
There are several things to know about this code snippet
There are several things to note in this snippet code:
The following effect will print those value
@Component({ template: `<button type="button" (click)="updateQuantity()"> {{quantity()}} </button>` }) export class QuantityComponent() { fruit = input.required<string>(); quantity = linkedSignal({ source: fruit, computation: () => 1 }); updateQuantity(): void { this.quantity.update(prevCount => prevCount++); } }
as explained above, by default the fruitId signal is untracked.
So how do you restart the http request each time the value of this signal changes, but also how do you cancel the previous request in the event that the value of the fruitId signal changes and the response to the previous request didn't arrive?
The resource function takes another property called request.
This property takes as its value a function that depends on signals and returns their value.
linkedSignal(computation: () => D, options?: { equal?: ValueEqualityFn<NoInfer<D>>; }): WritableSignal<D>; linkedSignal(options: { source: () => S; computation: (source: NoInfer<S>, previous?: { source: NoInfer<S>; value: NoInfer<D>; }) => D; equal?: ValueEqualityFn<NoInfer<D>>; }): WritableSignal<D>;
As shown in the code above, the loader function takes two parameters
So if the value of the fruitId signal changes during an httpRequest retrieving the details of a fruit, the request will be canceled to launch a new request.
Finally, Angular has also thought of the possibility of coupling this new api with RxJs, allowing us to benefit from the power of Rx operators.
Interporability is achieved using the rxResource function, which is defined in exactly the same way as the resource function.
The only difference will be the return type of the loader property, which will return an observable
@Component({ template: `<button type="button" (click)="updateQuantity()"> {{quantity()}} </button>` }) export class QuantityComponent() { fruit = input.required<string>(); count = signal(1); updateQuantity(): void { this.count.update(prevCount => prevCount++); } }
Here it's not necessary to have the abortSignal, the cancelling of the previous request when the value of the signal fruitId change is implicit in the function rxResource and the behaviour will be the same as the switchMap operator.
The above is the detailed content of The next improvement in Angular reactivity. For more information, please follow other related articles on the PHP Chinese website!