For a large Angular app on NgRx 22, how do you decide between the global Store and SignalStores, and where does signalState fit?
answer
- one tree vs many stores
- actions as a shared vocabulary
- DevTools and the action log
- lifetime follows the injector
- signalState: no DI, no protection
basics
~20 sNeither replaces the other in NgRx 22. Use the global Store where many features react to shared events and you want an action log and Store DevTools; SignalStores for feature- or view-scoped state; signalState for small private state in one class.
solid answer
~50 sThe global Store (`@ngrx/store`) gives one state tree changed only by dispatched actions through reducers, with selectors, effects, Store DevTools and runtime checks. That buys a shared event vocabulary, an action log and replay, at the cost of ceremony. A SignalStore is a scoped, injectable store whose methods change its own state directly with `patchState`; it can live at root or die with a component, and it has no official Redux DevTools connection. For event-driven flow within SignalStores there is the Events plugin (19.2). `signalState` is a lightweight container with no DI, features, hooks or protection, for one component's or service's private state. In a large app the answer is usually a mix, chosen per feature by who needs to react to a change, how long the state lives, and what the team must debug.
go deeper
Recall that NgRx has both the action-based global Store and the SignalStore, and that both are supported in NgRx 22.
Explain the mechanical differences: actions and reducers versus methods and patchState, one tree versus scoped instances, and what signalState leaves out.
Show where each has hurt you: global boilerplate for local state, SignalStores coordinating by calling each other, and missing action logs in bug hunts.
Own the rule for the whole app: criteria per feature, where cross-feature events flow, how debugging works, and when to revisit the split.
## Three tools from one library NgRx 22 ships three ways to hold state, and none is deprecated: - **Global Store** (`@ngrx/store` with `@ngrx/effects`): one immutable state tree for the app. Components `dispatch` actions; reducers built with `createReducer` compute the next state; selectors read it; effects run side effects in response to actions. - **SignalStore** (`signalStore()` from `@ngrx/signals`): an injectable class composed from features, holding state as signals, changed by its own methods with `patchState`. Provided at root, or per component or route. - **`signalState()`**: a function that returns a signal-based state container you can `patchState`, with no injection, features, hooks or protected state. ComponentStore still ships, but its docs direct new code to `@ngrx/signals`, so the modern decision is among these three. ## What each one buys and costs | Dimension | Global Store | SignalStore | `signalState` | |---|---|---|---| | Scope | one app-wide tree, lazily extended by features | root, route or component instance | a field in one class | | How state changes | actions through reducers | the store's own methods | any holder of it | | Cross-feature reaction | any reducer or effect can react to any action | direct calls, or the Events plugin | none | | Debugging | Store DevTools: action log, time travel | no official Redux DevTools connection | none | | Lifetime | the application | the injector that created it | the owning object | | Ceremony | high: actions, reducer, selectors, effects | medium: one file per store | minimal | ## Questions that drive the decision 1. **Who needs to react when this changes?** If adding a favourite in the conference planner must update the schedule, the attendee's badge, a recommendations panel and an analytics effect, a dispatched action that several reducers and effects listen to keeps those features decoupled. If only the schedule page cares, a SignalStore method is simpler. 2. **How long does the state live?** State that dies with a view, such as one open session's detail, fits a component-provided SignalStore, whose instance and hooks go away with the component. The global Store keeps everything until you remove it. 3. **What must the team debug?** An action log with time travel is valuable in a large, event-heavy app and for bug reports that need replay. A SignalStore gives you `getState` and `watchState` to build logging, and community features exist, but nothing official connects it to Redux DevTools. 4. **How much ceremony will the team carry?** Actions, reducers and selectors for a toggle on one page are overhead. Methods on a store are proportionate. 5. **Do you want event-driven flow without the global tree?** The Events plugin (`@ngrx/signals/events`, added in 19.2) brings named events and reducers to SignalStore, a middle path for teams that want decoupling but scoped stores. ## A realistic split for the planner - **Global Store:** attendee session and entitlements, and cross-cutting events such as "ticket upgraded" that several features react to. - **Root SignalStore:** the schedule, favourites and track filter, used by several pages but owned by one feature team. - **Component SignalStore:** per session-detail state that must reset each time a detail view opens. - **`signalState`:** a form wizard's step index and draft values, private to one component. The pieces interoperate. A SignalStore is an Angular provider, so its factories can `inject(Store)` and read global state with the Store's signal-based selection, or dispatch an action when something global must hear about a change. ## Risks of each extreme - **Everything in the global Store:** boilerplate for local state, state that outlives its view unless you clear it, and slow onboarding for small features. - **Everything in SignalStores:** stores calling each other's methods to coordinate, which recreates coupling the action model was meant to remove, and no shared debugging trail. - **`signalState` passed around:** it is writable by anyone holding it, so it belongs in a private field with only its signals exposed. ## How to present the decision A strong answer does not declare a winner. It names the criteria, chooses per feature, records the rule (for example: cross-feature events go through the global Store or the Events plugin, view state goes in component stores), and revisits it as the app grows.
- Can a SignalStore read and change state held in the NgRx global Store?Yes. A SignalStore is an Angular provider, so its `withProps` or `withMethods` factory can `inject(Store)`, read global state through the Store's signal-based selection and combine it in `withComputed`, and dispatch actions from its methods. The global Store knows nothing about SignalStores, so the dependency points one way.
- What debugging support do you give up when a feature moves from the global Store to a SignalStore?The action log and time travel of Store DevTools; the NgRx docs state there is no official Redux DevTools connection for `@ngrx/signals`. You can build logging with `getState` inside an Angular `effect()`, or `watchState` for every synchronous change, and the Events plugin restores named events you can trace.
saying these in an interview costs you the question
- SignalStore replaces the global Store, which is deprecated in NgRx 22.
- All state belongs in the global Store so that DevTools can see everything.
- SignalStore connects to Redux DevTools out of the box, like the global Store.
- signalState is a smaller SignalStore with the same protected state and hooks.
- Choosing SignalStore means giving up event-driven flow altogether.