Angular : migrer une application existante vers les signals et le mode zoneless
Les signals et la détection de changements sans Zone.js ne sont plus des expérimentations : ils forment désormais le socle recommandé d’Angular. Reste la question que se posent la plupart des équipes : comment y amener une application de production écrite avec des @Input(), des Observables partout et une dépendance implicite à Zone.js, sans tout réécrire ni casser l’existant ? Voici une démarche progressive, testée sur des projets réels.
Préparer le terrain : OnPush d’abord
Avant de toucher aux signals, généralisez ChangeDetectionStrategy.OnPush composant par composant. C’est l’étape qui révèle les vrais problèmes : un objet muté au lieu d’être remplacé, un setTimeout qui modifie l’état sans prévenir Angular, une librairie tierce qui écrit dans un champ du composant. Chaque bug corrigé ici est un bug que vous n’aurez pas à chasser en mode zoneless, car les deux modes reposent sur la même règle : la vue ne se met à jour que si Angular est explicitement notifié. Commencez par les composants feuilles (boutons, cartes, champs de formulaire), puis remontez vers les pages ; une bonne couverture de tests de composants rend cette phase beaucoup plus sereine.
Laisser les schematics faire le gros du travail
Angular fournit des migrations automatiques qui convertissent le code existant de manière sûre : ng generate @angular/core:signal-input-migration transforme les @Input() en input(), output-migration remplace les EventEmitter par output(), et signal-queries-migration s’occupe de @ViewChild et @ContentChild. Ces outils sont prudents : lorsqu’un input est réassigné en interne ou utilisé d’une manière ambiguë, ils le laissent intact et l’indiquent. Lancez-les dossier par dossier, relisez chaque diff et faites des commits atomiques ; un rollback ciblé reste ainsi toujours possible. Pour un input qui doit à la fois venir du parent et être modifié localement, model() ou linkedSignal() remplacent proprement les anciens contournements à base de ngOnChanges.
Faire cohabiter RxJS et signals
Migrer vers les signals ne signifie pas abandonner RxJS. La règle pragmatique : les signals pour l’état synchrone affiché dans les templates, RxJS pour les flux d’événements complexes (debounce de recherche, websockets, annulation de requêtes concurrentes). Le pont se fait avec toSignal() à la frontière entre un service et un composant, ce qui supprime au passage de nombreux pipes async et abonnements manuels. Pour le chargement de données, resource() et httpResource() exposent directement la valeur, l’état de chargement et l’erreur sous forme de signals ; vérifiez toutefois leur statut de stabilité dans votre version avant de les généraliser. Enfin, réservez effect() aux effets de bord réels (synchronisation avec le localStorage, logs, API externes) : dériver un état avec un effect plutôt qu’avec computed() est l’anti-pattern le plus fréquent.
Basculer en zoneless et vérifier
Une fois l’essentiel de l’état passé en signals, ajoutez provideZonelessChangeDetection() dans la configuration de l’application et retirez zone.js des polyfills dans angular.json. Les zones à risque sont connues : callbacks de librairies tierces (cartes, éditeurs riches, graphiques), code qui s’appuyait sur NgZone.onStable, et tests qui comptaient sur fakeAsync ou un detectChanges() implicite. Activez le zoneless dans les tests également, et passez l’application en revue avec le profiler des Angular DevTools pour vérifier que seuls les composants concernés se mettent à jour. Le gain se voit vite : bundle allégé de Zone.js, stack traces lisibles et cycles de détection nettement moins nombreux.
Planifier et chiffrer la migration
Pour un freelance ou une petite équipe, ce type de chantier se vend mieux découpé en lots qu’en « refonte » : audit et généralisation d’OnPush, migrations automatiques par domaine fonctionnel, conversion des services d’état, puis bascule zoneless avec une période de recette dédiée. Chaque lot livre une valeur mesurable (moins de régressions d’affichage, code plus simple à maintenir, meilleures performances) et peut être mis en production indépendamment. Mesurez avant et après : temps d’interaction, taille du bundle initial, nombre de cycles de détection sur les écrans critiques. Ces chiffres facilitent la discussion avec le client et justifient l’investissement.
Migrer vers les signals et le zoneless n’est pas un big bang : c’est une suite de petites étapes sûres, outillées par Angular lui-même. En commençant par OnPush, en laissant les schematics convertir le code mécanique et en gardant RxJS là où il excelle, une application existante peut adopter le modèle moderne d’Angular sans geler les évolutions fonctionnelles.
