Au moment où Angular 19 consolide les signaux comme primitive réactive centrale, le débat entre NgRx SignalStore et services à signaux n'est plus un choix binaire mais une question d'échelle et de complexité. SignalStore, introduit en Angular 17.3, est une abstraction légère construite sur les signaux, tandis qu'un service à signaux reste un simple singleton injecté contenant de la logique réactive. Le piège courant est de confondre légèreté avec absence de structure : un service à signaux n'est pas une alternative paresseuse à une vraie gestion d'état, c'est un outil pour des cas spécifiques. La différence ne se résume pas à la syntaxe ou au nombre de lignes de code, mais à la philosophie d'architecture et à la maintenabilité long terme.
Quand SignalStore devient incontournable
SignalStore excelle dans les scénarios où vous avez besoin d'une couche d'état centralisée avec plusieurs entités, des actions synchrones et asynchrones, et des sélecteurs complexes. Prenez une application e-commerce : panier, filtres de recherche, historique de commandes, notification de stock. SignalStore vous permet de modéliser chaque domaine comme un store distinct, avec des effets pour les appels API, des sélecteurs memoïzés et une traçabilité claire des mutations d'état. La syntaxe avec withState() , withComputed() et withMethods() crée une surface d'API cohérente et testable. Un exemple concret : gérer les filtres appliqués, les résultats de recherche et le cache côté client devient structuré et débogable. Les DevTools NgRx continuent de fonctionner, vous gardez donc la rétro-ingénierie des transitions d'état. Cette prévisibilité est précieuse dans les équipes, même petites : elle force une documentation implicite par la structure du code.
Cependant, SignalStore introduit une dépendance supplémentaire et une couche d'abstraction. Si votre équipe maîtrise déjà NgRx classique (reducer + action + effect), la migration vers SignalStore est douce mais non gratuite. Le compilateur Angular doit traiter la métaprogrammation des features, et même si le bundle reste léger, vous avez ajouté un concept intermédiaire entre le service pur et la machine d'état complète.
Les services à signaux : la flexibilité du pragmatisme
Un service à signaux est simplement un @Injectable() contenant des signal() , computed() et des méthodes de mutation. Aucune magie, aucune convention, juste du code TypeScript avec les primitives Angular. Cette approche brille quand l'état est peu complexe ou très localisé : un formulaire dynamique qui expose son état via signaux, un service de theme/configuration utilisateur, une liste de notifications. Vous écrivez moins de boilerplate et vous restez maître de chaque détail. L'exemple classique : un service ThemeService qui expose currentTheme = signal('light') et une méthode toggleTheme() . Aucune surcharge mentale, aucun pattern à mémoriser.
Le réel avantage des services à signaux est la flexibilité. Vous pouvez combiner état réactif, logique métier et effets secondaires dans une seule classe sans respecter une convention stricte. Si vous avez besoin d'un cas edge spécifique (par exemple, un debounce très particulier ou une fusion d'états provenant de plusieurs sources), vous le codez directement. Pas de friction, pas de débat sur où caser le comportement dans une architecture formelle. C'est pourquoi les services à signaux dominent dans les petits projets ou dans les modules très ciblés.
Intégration avec les composants
La différence d'intégration est frappante. Avec SignalStore, un composant injecte le store et accède à l'état via des sélecteurs : store.filteredResults() , store.isLoading() . Les sélecteurs sont des signaux computés, donc la détection de changement est granulaire et performante. Avec un service à signaux, c'est identique : themeService.currentTheme() dans le template ou computed() au niveau du composant. La mécanique réactive est la même, la syntaxe aussi. La différence réside dans la centralisabilité et la testabilité. Un SignalStore force une séparation claire : la logique d'état vit dans le store, le composant ne fait que consommer. Un service à signaux peut laisser la logique s'éparpiller entre le service et le composant, ce qui est un avantage en petite échelle mais devient chaotique à grande échelle.
Pour les effets asynchrones, SignalStore propose withEffects() et une intégration propre avec RxJS via rxMethod() . Un service à signaux doit gérer cela manuellement : vous créez des subjects, vous souscrivez, vous mettez à jour les signaux. C'est plus verbeux mais aussi plus explicite. Une requête HTTP qui déclenche un spinner, charge les données et met à jour un signal se code en quelques lignes avec rxMethod() dans SignalStore, mais aussi simplement dans un service avec un bon pattern d'effet secondaire.
Le piège du sur-engineering et de l'indécision
Le piège courant est de choisir SignalStore par défaut « parce que c'est moderne » ou par crainte de ne pas scaler, alors que 80 % du temps un service à signaux aurait suffi. Vous ajoutez de la complexité pour gérer une complexité potentielle. Inversement, refuser SignalStore par minimalisme et créer un chaos de services à signaux sans contrat clair est tout aussi dangereux. La bonne heuristique est simple : si vous avez plus d'une ou deux entités d'état, des mutations dépendantes les unes des autres, ou plusieurs composants qui doivent synchroniser l'état, SignalStore devient rentable. Si c'est juste un formulaire, une paire de filtres ou un service de configuration, un service à signaux suffit et reste plus clair.
Un autre piège : mélanger les deux sans convention. Un projet avec quatre SignalStores et trois services à signaux sans règle claire devient vite confus. Les développeurs ne savent pas où ajouter la prochaine feature. La clé est une convention d'équipe explicite : « Pour l'état global, on utilise SignalStore. Pour les services métier isolés, services à signaux. » Documentez-la, point.
Cas réels : architecture en couches
Prenez une application SaaS avec authentification, gestion de permissions, liste de projets, et un éditeur collaboratif. L'état d'authentification (utilisateur, token, permissions) peut être un SignalStore centralisé, car d'autres stores en dépendent. Les projets (liste, filtre, pagination) : SignalStore, car c'est une entité complexe avec plusieurs vues. L'éditeur collaboratif (contenu, curseurs des autres, état du document) : soit un SignalStore dédié si plusieurs composants le consomment, soit un service à signaux si c'est auto-contenu dans une modale. La theme/langue : service à signaux léger, injecté partout, zéro surcharge.
Cette stratégie mixte est pragmatique et scalable. Elle évite le dogmatisme et s'adapte à la vraie complexité du domaine.
Conclusion opérationnelle
Choisir entre NgRx SignalStore et services à signaux ne relève pas de la philosophie mais de l'économie du code et de la maintenabilité. SignalStore est votre outil quand l'état devient un problème architectural ; services à signaux quand l'état est un détail d'implémentation. Commencez simple, migrez vers SignalStore seulement quand vous sentirez la friction. Les signaux ont unifié la réactivité Angular ; il ne reste plus qu'à choisir l'enveloppe qui convient à votre cas.