Introduction
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?
Process of resetting one signal relative to another
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?
The LinkedSignal function to the rescue.
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.
const quantity = input.required<number>();
const price = linkedSignal(() => quantity() * 0);
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
- the source: the signal on which the computation function bases its re-evaluation
- the computation function
- a parameter object
Contrary to the “abbreviated” computation function, here the computation function takes as parameters the value of the source and a “precedent”.
const fruits = signal(['apple', 'orange']);
const choice = linkedSignal({
source: fruits,
computation: (source, previous) => {
if(!Boolean(previous)) {
return 'apple';
}
return previous.source.find(fruit => fruit === previous.value) ||
'apple';
}
})
The new Resource Api
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:
import { resource } from "@angular/core";
@Component()
export class FruitComponent {
fruitId = input.required<string>();
fruitRessource = resource({
loader: () => {
return fetch(`https://myFruit.com/${this.fruitId()}`).then(response =>
response.json());
},
});
fruitRessourceEffect(() => {
console.log("Status: ", this.todoResource.status());
console.log("Value: ", this.todoResource.value());
console.log("Error: ", this.todoResource.error());
})
}
There are several things to know about this code snippet
There are several things to note in this snippet code:
- by default, the loader takes a promise as parameter
- the fruitRessource type is a WrittableRessource, which will enable us to modify the data locally if required
- the request for fruit details is sent directly to the server when the resource fuitResource is created
- this.fruitId() is untracked in the loader, so no new request will be send if the fruitId change
- WrittableRessource have a method refresh to refresh the data
The following effect will print those value
Status: 'pending'
Value: undefined,
Error: undefined,
// When the promise is resolved, we enter again in the effect, because the status and value are signals and they changed
Status: 'resolved'
Value: { name: 'apple', count: 2 },
Error: undefined,
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.
import { resource } from "@angular/core";
@Component()
export class FruitComponent {
fruitId = input.required<string>();
fruitRessource = resource({
request: this.fruitId
loader: (request, abortSignal) => {
return fetch(`https://myFruit.com/${request}`}, { signal: abortSignal }).then(response =>
response.json());
},
});
fruitRessourceEffect(() => {
console.log("Status: ", this.todoResource.status());
console.log("Value: ", this.todoResource.value());
console.log("Error: ", this.todoResource.error());
})
}
As shown in the code above, the loader function takes two parameters
- the request value: here the value of the fruitId signal
- the value of the signal property of the abortController object used to cancel an http request.
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
import { resource } from "@angular/core";
@Component()
export class FruitComponent {
fruitId = input.required<string>();
fruitRessource = rxResource({
request: this.fruitId
loader: request => {
return this.httpClient.get<any>(`https://myFruit.com/${request}`)
}
});
fruitRessourceEffect(() => {
console.log("Status: ", this.todoResource.status());
console.log("Value: ", this.todoResource.value());
console.log("Error: ", this.todoResource.error());
})
}
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.
Top comments (0)