← Retour au blog
AngularNgRxSignalsState ManagementArchitecture

NgRx SignalStore vs Signal-Based Services: Beyond the Technical Choice

As Angular 19 consolidates signals as the central reactive primitive, the debate between NgRx SignalStore and signal-based services is no longer binary but a question of scale and complexity. SignalStore, introduced in Angular 17.3, is a lightweight abstraction built on signals, while a signal-based service remains a simple injected singleton containing reactive logic. The common pitfall is confusing lightness with lack of structure: a signal-based service is not a lazy alternative to real state management but a tool for specific scenarios. The difference is not about syntax or lines of code but about architectural philosophy and long-term maintainability.

When SignalStore becomes essential

SignalStore excels when you need a centralized state layer with multiple entities, synchronous and asynchronous actions, and complex selectors. Consider an e-commerce application: shopping cart, search filters, order history, stock notifications. SignalStore lets you model each domain as a distinct store, with effects for API calls, memoized selectors, and clear traceability of state mutations. The syntax with withState() , withComputed() , and withMethods() creates a cohesive and testable API surface. A concrete example: managing applied filters, search results, and client-side cache becomes structured and debuggable. NgRx DevTools continue to work, so you retain state transition reverse-engineering. This predictability is valuable in teams, even small ones: it enforces implicit documentation through code structure.

However, SignalStore introduces an additional dependency and abstraction layer. If your team already masters classic NgRx (reducer + action + effect), migrating to SignalStore is smooth but not free. The Angular compiler must process the metaprogramming of features, and while the bundle stays lightweight, you've added an intermediate concept between the pure service and the full state machine.

Signal-based services: the flexibility of pragmatism

A signal-based service is simply an @Injectable() containing signal() , computed() , and mutation methods. No magic, no conventions, just TypeScript code with Angular primitives. This approach shines when state is uncomplicated or highly localized: a dynamic form exposing its state via signals, a theme/user configuration service, a notification list. You write less boilerplate and retain full control of every detail. The classic example: a ThemeService exposing currentTheme = signal('light') and a toggleTheme() method. No mental overhead, no pattern to memorize.

The real advantage of signal-based services is flexibility. You can combine reactive state, business logic, and side effects in a single class without adhering to strict conventions. If you need a specific edge case (for instance, a very particular debounce or a fusion of states from multiple sources), you code it directly. No friction, no debate about where to place the behavior in a formal architecture. This is why signal-based services dominate in small projects or highly focused modules.

Integration with components

The integration difference is striking. With SignalStore, a component injects the store and accesses state via selectors: store.filteredResults() , store.isLoading() . Selectors are computed signals, so change detection is granular and performant. With a signal-based service, it's identical: themeService.currentTheme() in the template or computed() at the component level. The reactive mechanics are the same, the syntax identical. The difference lies in centralizability and testability. SignalStore enforces clear separation: state logic lives in the store, the component only consumes. A signal-based service can let logic scatter between service and component, which is an advantage at small scale but becomes chaotic at large scale.

For asynchronous effects, SignalStore offers withEffects() and clean RxJS integration via rxMethod() . A signal-based service must handle this manually: you create subjects, subscribe, update signals. It's more verbose but also more explicit. An HTTP request triggering a spinner, loading data, and updating a signal codes in a few lines with rxMethod() in SignalStore, but equally simply in a service with a solid side-effect pattern.

The pitfall of over-engineering and indecision

The common pitfall is choosing SignalStore by default 'because it's modern' or from fear of not scaling, when 80 % of the time a signal-based service would suffice. You add complexity to handle potential complexity. Conversely, refusing SignalStore from minimalism and creating chaos with signal-based services lacking clear contracts is equally dangerous. The good heuristic is simple: if you have more than one or two state entities, mutations dependent on each other, or multiple components needing to synchronize state, SignalStore becomes worthwhile. If it's just a form, a pair of filters, or a configuration service, a signal-based service suffices and stays clearer.

Another pitfall: mixing both without convention. A project with four SignalStores and three signal-based services without clear rules quickly becomes confusing. Developers don't know where to add the next feature. The key is an explicit team convention: 'For global state, we use SignalStore. For isolated business services, signal-based services.' Document it, done.

Real cases: layered architecture

Take a SaaS application with authentication, permission management, project list, and a collaborative editor. Authentication state (user, token, permissions) can be a centralized SignalStore, since other stores depend on it. Projects (list, filter, pagination): SignalStore, because it's a complex entity with multiple views. Collaborative editor (content, other cursors, document state): either a dedicated SignalStore if multiple components consume it, or a signal-based service if self-contained in a modal. Theme/language: lightweight signal-based service, injected everywhere, zero overhead.

This mixed strategy is pragmatic and scalable. It avoids dogmatism and adapts to real domain complexity.

Operational conclusion

Choosing between NgRx SignalStore and signal-based services is not philosophy but code economy and maintainability. SignalStore is your tool when state becomes an architectural problem; signal-based services when state is an implementation detail. Start simple, migrate to SignalStore only when you feel the friction. Signals have unified Angular reactivity; all that remains is choosing the wrapper that fits your case.

Développeur Angular & Mobile freelance — Strasbourg.

© 2026 Emilien Pons — Tous droits réservés.Conçu avec Angular, PrimeNG et ❤️