Formation > Blog > Langage > Angular Signals : Qu’est-ce que c’est ?

Les signaux Angular introduisent plusieurs fonctionnalités à Angular qui vous permettront de simplifier votre développement et vous aideront à construire des applications plus rapides par défaut.

Dans cet article, nous verrons en détail les nombreuses capacités que possèdent Angular Signals et savoir comment les utiliser correctement.

Avant de se lancer

Vous avez la possibilité de suivre notre formation Angular sur 3 jours durant laquelle vous apprendrez à développer des applications Web interactives écrites en TypeScript, à créer vos propres composants réutilisables et à maitriser les différentes facettes de la technologie.

Si vous avez déjà de l’expérience sur Angular, nous vous proposons notre formation Angular Avancé. Vous apprendrez à résoudre des problèmes d’orchestration sur RxJS, l’amélioration des performances de votre site Web ou développer et gérer des composants Angular.

L’équipe Ambient IT

Qu’est-ce qu’Angular Signals ?

C’est un système qui suit régulièrement comment et où l’état est utilisé dans l’application afin de permettre au framework d’optimiser les mises à jour de rendu. Il y a des valeurs réactives définies dans le but qu’elles puissent exprimer des dépendances entre elles.

Signals

Un signal correspond à un programme dont la fonction principale est d’appeler une autre fonction (wrapper). Cette fonction se situe autour d’une valeur qui indique à l’utilisateur lorsqu’une valeur change. Ces signaux peuvent contenir toutes les valeurs possibles allant des simples primitives aux structures de données complexes. La lecture d’une valeur d’un signal se fait toujours grâce à la fonction getter qui permet à Angular de suivre où est utilisé le signal.

Il existe différents types de signaux, les signaux inscriptibles et les signaux calculés.

Signaux inscriptibles

Les signaux inscriptibles garantissent une API pour mettre à jour les valeurs. Il est possible de créer des signaux inscriptibles en utilisant la fonction signal :

const count = signal(0);

// Signals are getter functions - calling them reads their value.
console.log('The count is: ' + count());

Afin de changer la valeur d’un signal inscriptible, il est possible grâce au .set :

count.set(3);

L’opération .update() permet de calculer une nouvelle valeur grâce à une précédente valeur :

// Increment the count by 1.
count.update(value => value + 1);

Pendant l’utilisation de signaux qui contiennent des objets, il est important de déplacer directement l’objet. Cela signifie qu’il est possible d’ajouter une nouvelle valeur sans avoir à entièrement remplacer la précédente valeur déjà présente. Ce type de modification interne peut se faire en utilisant la méthode .mutate :

const todos = signal([{title: 'Learn signals', done: false}]);

todos.mutate(value => {
  // Change the first TODO in the array to 'done: true' without replacing it.
  value[0].done = true;
});

Ces signaux possèdent le type WritableSignal.

Signaux Calculés

Les signaux calculés tirent leur valeur de divers signaux. Pour définir un signal calculé, il suffit d’utiliser computed et spécifier une fonction de dérivation :

const count: WritableSignal<number> = signal(0);
const doubleCount: Signal<number> = computed(() => count() * 2);

Le doubleCount signal dépend de count. Lorsque les count sont mis à jour, le framework Angular sait qu’il faut qu’un signal doubleCount soit également actualisé.

Angular permet aux calculs d’être mémorisés et d’être évalués. La fonction de dérivation ne s’exécutera pas pour calculer sa valeur tant que doubleCount n’a pas été lue. Lorsque la valeur est calculée, cette dernière est mise en cache et les prochaines lectures du signal doubleCount renverront la valeur sans passer par un calcul. Si le count est amené à changer, il indique au doubleCount que sa valeur mise en cache n’est plus valide, et qu’elle sera recalculée seulement lors de la prochaine lecture de doubleCount.

Il est important de savoir que les signaux calculés et les signaux inscriptibles sont totalement différents. Il est impossible d’affecter directement des valeurs à un signal calculé, cela produira une erreur de compilation, car doubleCount n’est pas un WritableSignal.

Les dépendances de signals calculés sont dynamiques.

const showCount = signal(false);
const count = signal(0);
const conditionalCount = computed(() => {
  if (showCount()) {
    return `The count is ${count()}.`;
  } else {
    return 'Nothing to see here!';
  }
});

Cet exemple montre que le count signal n’est lu que de manière conditionnelle. Pendant la lecture de conditionalCount, si le showCount affiche false, cela signifie que les mises à jour de count n’entraîneront pas de recalcul.

Au contraire, si le conditionalCount est lu et que le showCount affiche true, la fonction de dérivation s’exécutera de manière autonome en envoyant un message qui indique la valeur de count. Il faut noter que les dépendances peuvent être ajoutées ou supprimées. Si showCount est défini sur false, alors count ne sera plus considéré comme une dépendance de conditionalCount.

Lectures des signaux dans onpush : les composants

Lorsqu’un OnPush composant utilise la valeur d’un signal dans son modèle, Angular tentera de suivre le signal telle une dépendance du composant. Après que le signal ait été actualisé, le framework marquera automatiquement le composant pour faire en sorte que dans la prochaine détection de changement le composant se met à jour.

Les effets

Un effet est une opération qui s’exécute lorsqu’une ou plusieurs valeurs de signal changent. Il est possible de créer un effet via la fonction effect :

effect(() => {
  console.log(`The current count is: ${count()}`);
});

Les effets vont toujours s’exécuter au moins une fois. Lorsqu’un effet s’exécute, il va suivre toute la valeur de signal lue. Chaque fois qu’une des valeurs de signal change, l’effet s’exécute en amont. De plus, les effets gardent une trace de leurs dépendances de façon dynamique et ils ne suivent que les signaux lus pendant l’exécution. Ces effets s’exécutent toujours de manière asynchrone lors du processus de détection des modifications.

Utilisation des effets

Les effets sont utiles dans des conditions spécifiques. En effet, il existe des situations dans lesquelles la fonction effect sera sollicité :

  • Ajout d’un comportement DOM personnalisé ne pouvant pas être exprimé avec la syntaxe du modèle
  • Enregistrement des données affichées et de leur modification
  • Exécution d’un rendu personnalisé dans une bibliothèque de graphiques ou une autre bibliothèque d’interface utilisateur
  • Synchronisation des données avec window.localstorage

Par contre, il existe également des cas où la fonction effect est à éviter. Il faut éviter de l’utiliser pour la propagation des changements d’état car cela pourrait entraîner des erreurs, des mises à jour circulaires infinies, ou bien des cycles de détection de changement inutile.

Contexte d’injection

Par défaut, l’enregistrement d’un nouvel effet requiert un contexte d’injection. Pour cela, il est nécessaire d’utiliser la fonction effect dans un composant, un service constructor ou une directive :

@Component({...})
export class EffectiveCounterCmp {
  readonly count = signal(0);
  constructor() {
    // Register a new effect.
    effect(() => {
      console.log(`The count is: ${this.count()})`);
    });
  }
}

Par ailleurs, l’effet peut être affecté à un autre champ :

@Component({...})
export class EffectiveCounterCmp {
  readonly count = signal(0);
  
  private loggingEffect = effect(() => {
    console.log(`The count is: ${this.count()})`);
  });
}

Afin de créer un effet hors du constructor, il est possible de passer un Injector à effect par ces options :

@Component({...})
export class EffectiveCounterCmp {
  readonly count = signal(0);
  constructor(private injector: Injector) {}

  initializeLogging(): void {
    effect(() => {
      console.log(`The count is: ${this.count()})`);
    }, {injector: this.injector});
  }
}

Les effets destructeurs

Un effet est automatiquement détruit lorsque son contexte d’injection est détruit. Tous les effets créés dans les composants vont être détruits, il en va de même pour les effets au sein des directives ou des services. Par ailleurs, les effets renvoient un EffectRef utile pour détruire manuellement les effets à l’aide de l’opération .destroy(). Cette opération peut être combinée avec l’option manualCleanup qui crée un effet qui va durer jusqu’à sa destruction manuelle. Il est important de détruire les effets qui ne sont plus utiles.

Fonctions avancées des signaux

Fonctions d’égalité des signaux

Pendant la création d’un signal, il est possible de fournir une fonction d’égalité qui vérifiera si la nouvelle valeur est différente de la précédente.

import _ from 'lodash';

const data = signal(['test'], {equal: _.isEqual});

// Even though this is a different array instance, the deep equality
// function will consider the values to be equal, and the signal won't
// trigger any updates.
data.set(['test']);

Ces fonctions d’égalité peuvent être utilisées pour les signaux calculés et les signaux inscriptibles. Cependant, concernant les signaux inscriptibles, .mutate() ne vérifie pas l’égalité, car il modifie la valeur actuelle sans produire de nouvelles références.

Lecture sans suivi de dépendances

Il n’est pas toujours nécessaire d’exécuter du code qui peut lire des signaux dans une fonction réactive comme computed ou effect sans créer de dépendance. Par exemple, si en cas de modification de currentUser, la valeur de a counter soit enregistrée, cela va créer un effet qui lira deux signaux :

effect(() => {
  console.log(`User set to `${currentUser()}` and the counter is ${counter()}`);
});

Dans cet exemple, un message est enregistré lorsque currentUser ou count change. Si l’effet ne doit être exécuté que lorsque currentUser change, alors la lecture de count n’est qu’accessoire et les changements de count ne doivent pas enregistrer un nouveau message.

La fonction untracked est utile lorsqu’un effet doit invoquer un code externe qui ne doit pas être considéré comme une dépendance :

effect(() => {
  const user = currentUser();
  untracked(() => {
    // If the `loggingService` reads signals, they won't be counted as
    // dependencies of this effect.
    this.loggingService.log(`User set to ${user}`);
  });
});

Fonctions de nettoyage des effets

Les effets peuvent lancer des opérations de longue durée, qui seront annulées si l’effet est détruit ou s’il s’exécute de nouveau avant la fin de la première opération. Lors de la création d’un effet, la fonction peut éventuellement accepter une fonction onCleanup comme premier paramètre. Cette fonction onCleanup permet d’enregistrer un rappel qui est déclenché avant le début de la prochaine exécution de l’effet ou lors de la destruction de l’effet.

effect((onCleanup) => {
  const user = currentUser();

  const timer = setTimeout(() => {
    console.log(`1 second ago, the user became ${user}`);
  }, 1000);

  onCleanup(() => {
    clearTimeout(timer);
  });
});

Vous savez désormais tout concernant les signaux d’Angular, d’autres améliorations et modifications seront très certainement apportées lors de la sortie des nouvelles versions d’Angular.

UNE QUESTION ? UN PROJET ? UN AUDIT DE CODE / D'INFRASTRUCTURE ?

Pour vos besoins d’expertise que vous ne trouvez nulle part ailleurs, n’hésitez pas à nous contacter.

ILS SE SONT FORMÉS CHEZ NOUS

partenaire sncf
partenaire hp
partenaire allianz
partenaire sfr
partenaire engie
partenaire boursorama
partenaire invivo
partenaire orange
partenaire psa
partenaire bnp
partenaire sncf
partenaire hp
partenaire allianz
partenaire sfr
partenaire engie
partenaire boursorama
partenaire invivo
partenaire orange
partenaire psa
partenaire bnp