Retour aux articles

Migrer et adopter les Angular Signals dans une application Angular 17/18 existante

Migrer et adopter les Angular Signals dans une application Angular 17/18 existante | Laty Gueye Samba - Développeur Full Stack Dakar Sénégal, Expert Java Spring Boot Angular

Introduction

Ce billet technique explique comment migrer et adopter les Angular Signals dans une application Angular 17/18 existante. L'objectif est de fournir une feuille de route pratique et des exemples concrets afin que l'équipe puisse effectuer une migration incrémentale, réduire la complexité liée à certains usages de RxJS et améliorer les performances et la maintenabilité.

Pourquoi adopter les Angular Signals ?

Les Signals offrent un modèle d'état réactif, plus direct et souvent plus performant pour la mise à jour de l'interface. Ils permettent :

  • des lectures et écritures d'état synchrones et simples,
  • des computed values (valeurs dérivées) sans souscriptions explicites,
  • une intégration native avec le moteur de rendu pour déclencher des mises à jour de vue de façon fine.

Planification de la migration

La migration doit être progressive. Une stratégie recommandée : auditer l'application, prioriser les services et composants critiques pour les performances, migrer les cas d'utilisation locaux d'état d'abord, puis traiter les flux applicatifs partagés et les intégrations stream/RxJS.

Étapes de haut niveau

1. Identifier les BehaviorSubject/Subject/ReplaySubject utilisés comme source d'état. 2. Remplacer les usages locaux d'état par des Signals dans les services. 3. Exposer l'état comme Signal en lecture seule aux composants. 4. Remplacer progressivement les sélecteurs RxJS par des computed. 5. Utiliser toSignal pour adapter les Observables restants.

Exemples pratiques

Migrer un BehaviorSubject de base vers un Signal

Exemple avant (RxJS) :

export class CounterService { private _count$ = new BehaviorSubject(0); public count$ = this._count$.asObservable(); increment() { this._count$.next(this._count$.value + 1); } }

Exemple après (Signals) :

import { signal, Signal } from '@angular/core'; export class CounterService { private _count = signal(0); public readonly count: Signal = this._count; increment() { this._count.update(n => n + 1); } }

Remarques :

  • La lecture s'effectue par appel : this.count().
  • La mise à jour privilégie update pour opérations atomiques.
  • L'exposition en lecture seule évite les modifications non contrôlées depuis les composants.

Utiliser computed pour les sélecteurs

Transformer un sélecteur RxJS en valeur dérivée :

import { computed } from '@angular/core'; const fullName = computed(() => `${user().firstName} ${user().lastName}`);

Le computed recalcule automatiquement quand ses dépendances changent, sans abonnement explicite.

Intégrer des Observables existants avec toSignal

Pour garder certains flux RxJS (API, WebSocket), utiliser toSignal :

import { toSignal } from '@angular/core/rxjs'; const userSignal = toSignal(userObservable, { initialValue: null });

Cela facilite la cohabitation entre RxJS et Signals pendant la migration.

Adoption progressive dans les composants

Les composants peuvent consommer directement des Signal exposés par les services. Dans les templates Angular, appeler le signal actualise la vue :

<button (click)="counter.increment()">Increment</button> <p>Count: {{ counter.count() }}</p>

Les Signals permettent de réduire le nombre d'abonnements manuels et d'appeler moins souvent markForCheck quand la logique reste dans le modèle réactif.

Effets pour les opérations secondaires

Pour exécuter des effets de bord quand un signal change (log, navigation, appel tiers), utiliser effect :

import { effect } from '@angular/core'; effect(() => { const value = this._count(); console.log('Count changed', value); });

Les effets s'exécutent de façon synchrone lors des mutations de signal et peuvent être nettoyés si nécessaire.

Bonnes pratiques

  • Encapsuler l'état : garder les signaux privés et exposer des Signal en lecture seule.
  • Préférer update pour les modifications dépendantes de l'ancien état.
  • Utiliser computed pour centraliser la logique dérivée et éviter les recalculs inutiles dans les templates.
  • Conserver RxJS pour les flux complexes, combinaisons asynchrones et opérateurs avancés pendant la transition.
  • Tester progressivement : couvrir les services migrés avec des tests unitaires pour garantir le comportement.

Pièges et points d'attention

Quelques risques à anticiper :

  • Sémantique synchrone : les signals sont majoritairement synchrones ; les migrations doivent tenir compte de changements de timing par rapport à des Observables asynchrones.
  • Interopérabilité : bien utiliser toSignal et toObservable si nécessaire pour cohabiter avec l'écosystème RxJS.
  • Performance : éviter de rendre des objets mutables observables via signal sans attention ; préférer des copies immuables pour les gros états.
  • Apprentissage : former l'équipe aux patterns Signals (effect, computed, toSignal) pour éviter les anti-patterns.

Exemple de migration incrémentale conseillée

Proposition d'itérations :

  1. Migrer les services internes sans dépendances externes.
  2. Exposer les signaux aux composants et vérifier les performances et les tests.
  3. Remplacer les sélecteurs RxJS par des computed quand pertinent.
  4. Utiliser toSignal pour les Observables restants et planifier leur remplacement ou leur conservation.

Conclusion

L'adoption des Angular Signals peut simplifier la gestion d'état et améliorer la performance d'une application Angular 17/18. Une migration progressive, accompagnée de bonnes pratiques (encapsulation, computed, effets), permet de réduire les risques. L'équipe gagne en lisibilité du code et en réactivité fine sans abandonner immédiatement RxJS pour les cas complexes.

À propos de l'auteur

Laty Gueye Samba est développeur Full Stack basé à Dakar, Sénégal. Spécialiste des écosystèmes Java / Spring Boot et Angular.

Contact : latygueyesamba@gmail.com  |  Dakar, Sénégal