Depuis les dernières versions d'Angular, un nouveau système de réactivité primitive a été développé dans le cadre : les signaux !
Aujourd'hui, avec le recul, nous nous rendons compte que certains cas d'usage n'avaient pas été abordés, et évidemment l'équipe Angular étant très réactive nous fournira des aides pour couvrir ces cas d'usage.
Quels sont ces cas d’utilisation ? Quelles solutions vont être mises en place, et comment vont-elles être utilisées ?
Commençons par illustrer ce problème.
Imaginons que nous ayons une corbeille de fruits avec une certaine quantité.
La quantité est gérée par un composant qui introduit le 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++); } }
Ici, la variable doit être réinitialisée si le prix d'entrée du fruit change.
Une solution simple serait d'utiliser un effet
@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++); } }
Le code précédent est une mauvaise pratique. Pourquoi est-ce la grande question ?
Nous devons définir l'option signalWrites sur true afin de définir la quantité de signal. Cela est dû à une mauvaise interprétation du problème posé.
Dans notre cas, nous souhaitons synchroniser deux variables qui, dans notre matérialisation, sont désynchronisées
Le comptoir n'est pas indépendant du fruit, qui est notre source initiale. En réalité, nous avons ici un état composant, dont la source initiale est le fruit et le reste est un dérivé du fruit.
Matérialisez le problème comme suit
@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++); } }
Cette matérialisation lie fortement le fruit à sa quantité.
Ainsi dès que le fruit change, la variable calculée fruitState est automatiquement recalculée. Ce recalcul renvoie un objet avec la propriété quantité, qui est un signal initialisé à 1.
En renvoyant un signal, la variable peut être incrémentée au clic et simplement réinitialisée lorsque le fruit change.
C'est un patron relativement simple à mettre en place, mais ne peut-on pas le simplifier ?
Avec l'arrivée d'Angular 19, une nouvelle fonction de calcul de signaux dérivés.
Jusqu'à présent, nous avions la fonction calculée, mais cette fonction renvoie un Signal et non un WrittableSignal, ce qui aurait été pratique dans notre précédent cas d'utilisation de la variable quantité.
C'est là qu'intervient LinkedSignal. LinkedSignal, comme son nom l'indique, vous permet de lier fortement deux signaux entre eux.
Si l'on revient à notre cas précédent, cette fonction nous permettrait de simplifier le code ainsi :
@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++); } }
La fonction linkedSignal est définie comme suit :
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>;
Dans la première définition, la définition « abrégée », la fonction linkedSignal prend une fonction de calcul comme paramètre et un objet de configuration.
@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++); } }
Dans cet exemple précédent, comme la fonction de calcul dépend du signal de quantité, lorsque la quantité change, la fonction de calcul est réévaluée.
Dans la deuxième définition, la méthode linkedFunction prend en paramètre un objet avec trois propriétés
Contrairement à la fonction de calcul « abrégée », ici la fonction de calcul prend en paramètres la valeur de la source et un « précédent ».
@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 introduira une nouvelle API pour une récupération simple des données et la récupération de l'état des requêtes (en attente, etc.), des données et des erreurs.
Pour ceux qui connaissent un peu le framework, cette nouvelle API fonctionne un peu comme le hook useRessource.
Regardons un exemple :
@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++); } }
Il y a plusieurs choses à savoir sur cet extrait de code
Il y a plusieurs choses à noter dans cet extrait de code :
L'effet suivant imprimera ces valeurs
@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++); } }
comme expliqué ci-dessus, par défaut, le signal fruitId n'est pas suivi.
Alors comment redémarrer la requête http à chaque fois que la valeur de ce signal change, mais aussi comment annuler la requête précédente dans le cas où la valeur du signal fruitId change et que la réponse à la requête précédente ne change pas arriver ?
La fonction ressource prend une autre propriété appelée request.
Cette propriété prend comme valeur une fonction qui dépend des signaux et renvoie leur valeur.
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>;
Comme indiqué dans le code ci-dessus, la fonction de chargement prend deux paramètres
Donc si la valeur du signal fruitId change lors d'une httpRequest récupérant les détails d'un fruit, la requête sera annulée pour lancer une nouvelle requête.
Enfin, Angular a également pensé à la possibilité de coupler cette nouvelle api avec RxJs, nous permettant ainsi de bénéficier de la puissance des opérateurs Rx.
L'interporabilité est obtenue à l'aide de la fonction rxResource, qui est définie exactement de la même manière que la fonction ressource.
La seule différence sera le type de retour de la propriété loader, qui renverra un 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++); } }
Ici il n'est pas nécessaire d'avoir le abortSignal, l'annulation de la requête précédente lorsque la valeur du signal fruitId change est implicite dans la fonction rxResource et le comportement sera le même que celui de l'opérateur switchMap.
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!