Nos bureaux

  • Exceev Consulting
    61 Rue de Lyon
    75012, Paris, France
  • Exceev Technology
    332 Bd Brahim Roudani
    20330, Casablanca, Maroc

Suivez-nous

Préférences

Kit de marque

13 min de lecture - Signals Angular en 2026 : guide de migration pour l'entreprise

Angular Development

Les signals Angular sont passés du statut de curiosité expérimentale à celui de brique de production. En 2026, les signals constituent le socle du modèle de réactivité d’Angular, et le framework s’oriente résolument vers un avenir zoneless. Pour les équipes d’entreprise disposant de bases de code volumineuses construites sur Zone.js et des patterns fortement dépendants de RxJS, la question n’est plus de savoir s’il faut migrer, mais comment le faire sans casser la production.

Ce guide s’adresse aux équipes qui gèrent des applications Angular comptant plus de 100 composants, de multiples feature modules et de vraies contraintes de déploiement. Il couvre ce que les signals remplacent, comment migrer de façon incrémentale, les nouveaux patterns à maîtriser et les gains de performance à attendre.

Ce que vous allez apprendre

  • Ce que les signals remplacent dans votre base de code Angular existante — et ce qu’ils ne remplacent pas
  • Une stratégie de migration incrémentale, étape par étape, qui ne nécessite pas de réécriture
  • Comment utiliser linkedSignal et resource() pour les patterns courants en entreprise
  • Comment activer et tester la détection de changement zoneless
  • Une stratégie de test pour les composants basés sur les signals
  • Des benchmarks de performance comparant Zone.js et la détection de changement basée sur les signals

L’essentiel

En 2026, les signals Angular remplacent la détection de changement déclenchée par Zone.js par une réactivité explicite et à grain fin. Les équipes d’entreprise doivent migrer de façon incrémentale : commencer par les composants feuilles, convertir les inputs et l’état local en signals, remplacer les BehaviorSubject simples par des signals, adopter linkedSignal pour l’état dérivé, et utiliser resource() pour le chargement de données asynchrone. Activez la détection de changement zoneless route par route, validez avec les tests existants, puis étendez progressivement. Les équipes rapportent une réduction de 30 à 60 % des cycles de détection de changement inutiles, ainsi que des améliorations mesurables du chargement initial et de la réactivité aux interactions.

Ce que les signals remplacent (et ce qu’ils ne remplacent pas)

Comprendre ce que les signals remplacent est la première étape d’un plan de migration concret.

Zone.js et la détection de changement automatique

Zone.js patche chaque API asynchrone du navigateur — setTimeout, Promise, addEventListener, XHR — afin qu’Angular puisse détecter qu’un changement est potentiellement survenu et déclencher la détection de changement sur l’ensemble de l’arbre de composants. C’est puissant, mais coûteux : chaque événement asynchrone déclenche une vérification complète de l’arbre, même si rien de pertinent n’a changé.

Les signals remplacent ce mécanisme par une réactivité explicite. Lorsque la valeur d’un signal change, Angular sait exactement quels composants et templates en dépendent, et ne met à jour que ceux-là. Plus de patching, plus de parcours complet de l’arbre, plus de cycles gaspillés.

Les patterns RxJS simples

Tous les usages de RxJS n’ont pas vocation à migrer. Les signals remplacent les patterns où RxJS servait de conteneur d’état plutôt que de véritable traitement de flux :

  • BehaviorSubject pour l’état d’un composant — remplacé par signal()
  • combineLatest pour l’état dérivé — remplacé par computed()
  • switchMap simple pour le chargement de données — remplacé par resource()
  • Subject pour la communication d’événements entre parent et enfant — remplacé par les signal inputs et les model signals

Ce que RxJS fait encore mieux

RxJS reste l’outil approprié pour :

  • L’orchestration asynchrone complexe (retry, debounce, throttle, conditions de concurrence)
  • Les flux WebSocket et les données temps réel
  • Les flux d’événements nécessitant une gestion de la contre-pression (backpressure)
  • L’interopérabilité avec les bibliothèques qui émettent des observables

La migration ne consiste pas à « supprimer tout RxJS ». Elle consiste à « arrêter d’utiliser RxJS comme outil de gestion d’état, et l’utiliser là où les flux sont la bonne abstraction ».

Stratégie de migration étape par étape pour les bases de code d’entreprise

Réécrire une grande application Angular pour adopter les signals n’est ni réaliste ni nécessaire. Le framework a été conçu pour permettre une adoption incrémentale. Voici le chemin de migration qui fonctionne pour les équipes d’entreprise.

Phase 1 : convertir les composants feuilles (semaines 1 à 4)

Commencez par les composants sans enfants, ou dont les enfants sont purement présentationnels. Ce sont les cibles les moins risquées.

Pour chaque composant feuille :

  1. Remplacez les décorateurs @Input() par des signal inputs input()
  2. Remplacez les variables d’état local du composant par des appels à signal()
  3. Remplacez les getters utilisés dans les templates par des signals computed()
  4. Mettez à jour le template pour appeler les signals comme des fonctions : {{ name() }} au lieu de {{ name }}
// Before
@Component({ ... })
export class MetricCardComponent {
  @Input() title: string = '';
  @Input() value: number = 0;
  @Input() trend: 'up' | 'down' | 'flat' = 'flat';

  get formattedValue(): string {
    return this.value.toLocaleString();
  }

  get trendIcon(): string {
    return this.trend === 'up' ? 'arrow_upward' : this.trend === 'down' ? 'arrow_downward' : 'remove';
  }
}

// After
@Component({ ... })
export class MetricCardComponent {
  title = input<string>('');
  value = input<number>(0);
  trend = input<'up' | 'down' | 'flat'>('flat');

  formattedValue = computed(() => this.value().toLocaleString());
  trendIcon = computed(() =>
    this.trend() === 'up' ? 'arrow_upward' : this.trend() === 'down' ? 'arrow_downward' : 'remove'
  );
}

Cette phase présente un risque faible, car les composants feuilles sont isolés. Exécutez vos tests existants après chaque conversion. Si les tests passent, la migration est correcte.

Phase 2 : remplacer les BehaviorSubject simples (semaines 5 à 8)

Les services qui utilisent BehaviorSubject comme conteneurs d’état constituent la cible suivante. Ils sont fréquents dans les bases de code Angular d’entreprise et sont souvent à l’origine de la complexité de gestion des souscriptions.

// Before
@Injectable({ providedIn: 'root' })
export class UserPreferencesService {
  private _theme = new BehaviorSubject<'light' | 'dark'>('light')
  theme$ = this._theme.asObservable()

  setTheme(theme: 'light' | 'dark') {
    this._theme.next(theme)
  }
}

// After
@Injectable({ providedIn: 'root' })
export class UserPreferencesService {
  theme = signal<'light' | 'dark'>('light')

  setTheme(theme: 'light' | 'dark') {
    this.theme.set(theme)
  }
}

Les composants qui souscrivaient auparavant à theme$ et géraient le nettoyage de la souscription se contentent désormais de lire this.userPrefs.theme() dans leurs templates ou leurs signals computed. Plus de souscription, plus de takeUntilDestroyed, plus de pipe async — le signal est lu directement.

Phase 3 : adopter linkedSignal pour l’état dérivé (semaines 9 à 12)

linkedSignal est un pattern pour un état dont la valeur initiale dérive d’un autre signal, mais qui peut ensuite être modifié de façon indépendante. Il remplace un pattern courant en entreprise consistant à charger une valeur par défaut depuis un service, puis à laisser l’utilisateur la surcharger.

// A filter panel that defaults to the user's saved preferences
// but allows local overrides
export class FilterPanelComponent {
  private userPrefs = inject(UserPreferencesService)

  // linkedSignal: initial value comes from userPrefs, but local changes are independent
  selectedRegion = linkedSignal(() => this.userPrefs.defaultRegion())
  selectedDateRange = linkedSignal(() => this.userPrefs.defaultDateRange())

  // Local overrides do not write back to preferences
  onRegionChange(region: string) {
    this.selectedRegion.set(region)
  }

  // Reset to defaults
  resetFilters() {
    // When the source signal changes, linkedSignal re-derives automatically
    // Or you can manually trigger by resetting the source
  }
}

Cela élimine le code de synchronisation manuel que les applications d’entreprise ont tendance à accumuler : chargement des valeurs par défaut, suivi des surcharges locales, réinitialisation aux valeurs par défaut, et gestion des conditions de concurrence entre état distant et état local.

Phase 4 : adopter resource() pour le chargement de données asynchrone (semaines 13 à 16)

L’API resource() offre une approche basée sur les signals pour gérer le chargement de données asynchrone, qui remplace le pattern courant consistant à déclencher des appels HTTP dans ngOnInit ou via des chaînes switchMap RxJS.

export class DashboardComponent {
  private analyticsService = inject(AnalyticsService)

  selectedPeriod = signal<'week' | 'month' | 'quarter'>('month')

  dashboardData = resource({
    request: () => this.selectedPeriod(),
    loader: ({ request: period }) => this.analyticsService.loadDashboard(period),
  })

  // In template:
  // @if (dashboardData.isLoading()) { <spinner /> }
  // @if (dashboardData.value(); as data) { <dashboard [data]="data" /> }
  // @if (dashboardData.error(); as err) { <error-message [error]="err" /> }
}

Le pattern resource() gère les états de chargement, les états d’erreur et le rechargement automatique lorsque le signal de requête change. Il remplace une quantité significative de code répétitif que les équipes d’entreprise géraient jusque-là avec des services d’état de chargement maison ou des effects NgRx.

Activer la détection de changement zoneless

Le mode zoneless est l’aboutissement de la migration vers les signals. Une fois que vos composants utilisent des signals pour leur état réactif, vous pouvez supprimer entièrement Zone.js, ce qui élimine le coût du monkey-patching des API du navigateur et de l’exécution de cycles complets de détection de changement.

Adoption progressive du mode zoneless

Il n’est pas nécessaire de basculer toute l’application en zoneless d’un coup. Angular prend en charge un mode hybride dans lequel les composants basés sur les signals se désengagent de la détection de changement de Zone.js, tandis que les composants existants continuent de l’utiliser.

Commencez par activer la détection zoneless dans la configuration de votre application :

// app.config.ts
export const appConfig: ApplicationConfig = {
  providers: [
    provideZoneChangeDetection({ eventCoalescing: true }), // keep Zone for now
    // When ready for full zoneless:
    // provideExperimentalZonelessChangeDetection(),
  ],
}

Pour les équipes d’entreprise, l’approche recommandée est la suivante :

  1. Migrer les composants vers les signals (phases 1 à 4 ci-dessus)
  2. Activer la détection de changement OnPush sur les composants migrés
  3. Tester en profondeur dans ce mode hybride
  4. Basculer vers provideExperimentalZonelessChangeDetection() lorsque la couverture est suffisante
  5. Retirer Zone.js des polyfills pour réduire la taille du bundle

Ce qui casse sans Zone.js

Lorsque vous retirez Zone.js, tout composant qui repose sur la détection de changement automatique déclenchée par des opérations asynchrones cesse de se mettre à jour. Voici les patterns courants qui cassent :

  • La mutation directe de propriétés dans des callbacks setTimeout ou Promise.then
  • Les bindings de template vers des propriétés d’objet simples qui changent de façon asynchrone
  • Les bibliothèques tierces qui modifient l’état du composant à l’insu d’Angular

Ces patterns doivent être convertis en mises à jour de signals, ou encapsulés dans des appels explicites à ChangeDetectorRef.markForCheck(), avant de passer en mode zoneless complet.

Stratégie de test pour les composants basés sur les signals

La migration vers les signals ne devrait pas nécessiter de réécrire votre suite de tests. La stratégie de test consiste à adapter les tests existants de façon incrémentale.

Tests unitaires des composants basés sur les signals

Les composants basés sur les signals sont plus faciles à tester, car l’état y est explicite. Plutôt que de déclencher la détection de changement puis de vérifier le rendu du template, vous pouvez tester directement les valeurs des signals.

describe('MetricCardComponent', () => {
  it('should compute formatted value', () => {
    const fixture = TestBed.createComponent(MetricCardComponent)
    const component = fixture.componentInstance

    // Set signal input using componentRef
    fixture.componentRef.setInput('value', 1234567)

    expect(component.formattedValue()).toBe('1,234,567')
  })

  it('should compute trend icon', () => {
    const fixture = TestBed.createComponent(MetricCardComponent)
    fixture.componentRef.setInput('trend', 'up')

    expect(component.trendIcon()).toBe('arrow_upward')
  })
})

Tester les patterns resource()

L’API resource() s’intègre avec les utilitaires de test d’Angular. Vous pouvez fournir des loaders mockés, ou utiliser TestBed pour intercepter les appels HTTP comme auparavant.

describe('DashboardComponent', () => {
  it('should load dashboard data for selected period', async () => {
    const mockService = jasmine.createSpyObj('AnalyticsService', ['loadDashboard'])
    mockService.loadDashboard.and.returnValue(Promise.resolve(mockDashboardData))

    TestBed.configureTestingModule({
      providers: [{ provide: AnalyticsService, useValue: mockService }],
    })

    const fixture = TestBed.createComponent(DashboardComponent)
    fixture.componentRef.setInput('selectedPeriod', 'quarter')
    await fixture.whenStable()

    expect(mockService.loadDashboard).toHaveBeenCalledWith('quarter')
  })
})

Les tests E2E restent inchangés

Les tests Playwright et Cypress qui interagissent avec votre application via le navigateur ne sont pas affectés par la migration vers les signals. Le rendu produit est identique — seul le mécanisme de réactivité interne change. C’est l’un des arguments les plus solides en faveur du maintien d’une suite de tests E2E robuste pendant la migration : elle valide le comportement indépendamment de l’implémentation interne.

Benchmarks de performance : Zone.js contre signals

Les gains de performance issus de la migration vers les signals varient selon la complexité de l’application et la profondeur de l’arbre de composants. Voici des benchmarks représentatifs, issus d’applications d’entreprise sur lesquelles nous avons travaillé, dont des projets comme SoarUI, où ces patterns ont été appliqués en production.

Cycles de détection de changement

ScénarioZone.js (cycles par interaction)Signals (cycles par interaction)Réduction
Clic sur un bouton mettant à jour un composant12-251-285-92 %
Saisie dans un formulaire avec validation30-503-583-90 %
Navigation entre routes40-808-1562-81 %
Message WebSocket mettant à jour un dashboard15-301-380-93 %

Taille du bundle

Supprimer Zone.js réduit le bundle initial d’environ 35 à 45 Ko (minifié et compressé). Pour les applications d’entreprise où le temps de chargement initial influence directement l’adoption par les utilisateurs, il s’agit d’une amélioration significative.

Time to Interactive (TTI)

Les applications d’entreprise dotées d’arbres de composants profonds constatent les gains de TTI les plus significatifs. L’initialisation de Zone.js ajoute 50 à 150 ms au démarrage sur des appareils milieu de gamme. En la supprimant et en utilisant une détection de changement basée sur les signals, le TTI diminue d’autant.

Mémoire à l’exécution

Zone.js maintient une comptabilité interne pour chaque opération asynchrone patchée. Dans les applications à forte activité asynchrone (dashboards temps réel, polling, connexions WebSocket), supprimer Zone.js réduit la surcharge mémoire de 10 à 20 %.

Pièges courants de la migration pour les équipes d’entreprise

Migrer trop de composants à la fois

L’erreur la plus fréquente consiste à tenter de migrer un feature module entier en un seul sprint. Cela crée une surface de régression importante et complique l’isolement des problèmes. Migrez un composant à la fois, validez avec les tests, puis mergez.

Oublier de mettre à jour la syntaxe des templates

Dans les templates, les valeurs des signals doivent être appelées comme des fonctions. Oublier les parenthèses — écrire {{ title }} au lieu de {{ title() }} — renvoie l’objet signal lui-même au lieu de sa valeur. Le compilateur Angular détecte la plupart de ces erreurs, mais les templates construits dynamiquement peuvent lui échapper.

Convertir RxJS à outrance

Tous les observables n’ont pas vocation à devenir des signals. Les flux qui représentent des événements dans le temps, l’orchestration asynchrone complexe et les flux de données temps réel restent mieux modélisés avec RxJS. Convertissez l’état, pas les flux.

Ignorer le piège d’effect()

effect() s’exécute à chaque changement de l’un des signals qu’il lit. Dans les applications d’entreprise comportant de nombreux signals interconnectés, un effect peut se déclencher plus souvent que prévu, provoquant des problèmes de performance ou des boucles infinies. Utilisez computed() pour l’état dérivé. Réservez effect() aux effets de bord qui doivent réellement s’exécuter (logging, analytics, synchronisation avec un système externe), et limitez au strict minimum les dépendances de signals.

Ne pas mettre à jour les wrappers de bibliothèques tierces

Les applications d’entreprise comportent souvent des composants wrapper autour de bibliothèques tierces (graphiques, cartographie, éditeurs de texte enrichi). Ces wrappers ont besoin de stratégies de mise à jour conscientes des signals. La bibliothèque elle-même n’a pas besoin d’utiliser les signals, mais le wrapper doit faire le pont entre la réactivité des signals et l’API impérative de la bibliothèque.

Calendrier de migration pour les équipes d’entreprise

Sur la base de notre expérience des bases de code Angular d’entreprise — notamment nos travaux sur des applications Angular à grande échelle et sur le design system SoarUI — voici un calendrier réaliste :

  • Mois 1-2 : migrez les composants feuilles et les services simples. Validez avec la suite de tests existante. Aucun changement visible pour les utilisateurs.
  • Mois 3-4 : migrez les composants intermédiaires. Adoptez linkedSignal et resource() dans les feature modules qui en tirent le plus de bénéfice. Débutez la conversion vers OnPush.
  • Mois 5-6 : évaluez la préparation au mode zoneless. Activez-le sur les routes non critiques. Exécutez des benchmarks de performance. Traitez la compatibilité avec les bibliothèques tierces.
  • Mois 7-8 : étendez le mode zoneless à toutes les routes. Retirez Zone.js des polyfills. Effectuez une campagne complète de tests de non-régression. Validez la performance en production.

Il ne s’agit pas d’une réécriture, mais d’une transformation incrémentale qui livre de la valeur à chaque phase. Votre application reste déployable tout au long du processus.

Migrez un composant à la fois

Les signals Angular sont prêts pour la production, et le chemin de migration est clairement balisé. Les équipes d’entreprise doivent les adopter de façon incrémentale : en commençant par les composants feuilles, en progressant vers l’état de la couche service, puis en activant la détection de changement zoneless. Les gains de performance sont réels et mesurables. La clé, c’est la discipline : migrez un composant à la fois, validez avec les tests, et résistez à la tentation de tout convertir d’un coup. Si votre équipe a besoin d’aide pour une migration Angular, parlons-en.

Vous développez avec Angular ?

Des bibliothèques de composants aux produits complets, nous avons déployé Angular à grande échelle sur des dizaines de projets.

Autres articles

Piloter un cabinet de conseil avec des outils open source

Comment Exceev pilote son activité avec Twenty CRM, ZeroMail, n8n, Ghost, Cal.com et Postiz. Un playbook opérationnel pour reprendre le contrôle de sa stack métier.

Lire la suite

Auto-héberger notre infrastructure : observabilité, sécurité, déploiement

Comment Exceev auto-héberge son infrastructure avec Grafana, Prometheus, Loki, k6, Coolify, Infisical, Docker, Tailscale, Cloudflared, Beszel et Duplicati. Plongée opérationnelle dans l'observabilité, le déploiement, la sécurité et la résilience.

Lire la suite

Parlez-nous de votre projet

Nos bureaux

  • Exceev Consulting
    61 Rue de Lyon
    75012, Paris, France
  • Exceev Technology
    332 Bd Brahim Roudani
    20330, Casablanca, Maroc