Angular State Options: Signals vs RxJS vs NgRx vs Service The state decision tree from earlier in this chapter says when to climb a level. This lesson is about what to reach for once Angular is the framework — because Angular ships with more state tools than most ecosystems, and the most common mistake isn't picking the wrong one. It's picking NgRx by convention, on day one, before any level in the tree actually called for it. Mapping the tree onto Angular's toolkit Levels 1–2 (local, lifted): signals. A inside a component, or lifted to a parent and passed down via . No service, no store, no ceremony — just a value. Level 3 (shared, cross-cutting): a service holding signals. This is the level most Angular teams skip straight past, and it's the one doing the most quiet work in a well-built app. Angular's dependency injection is the distribution mechanism — a signal-holding service gives every component that injects it the same live value, with no Context-equivalent boilerplate at all. Any component that injects reads live, reactive state — no action types, no reducers, no dispatch. For the overwhelming majority of "I need shared state" cases in a real Angular app, this is the entire solution. Level 4 (global store, many writers, needs devtools): NgRx. Actions, reducers, effects, selectors, time-travel debugging — genuinely earned when a feature has many independent writers and update logic complex enough that tracing "what changed this, and why" by hand becomes impractical. Level 5 (server state): resource APIs. Covered in the previous lesson — not s refetched by hand. The Angular-specific trap: NgRx as a default, not a decision Angular's own history makes this mistake unusually easy to fall into. NgRx has long been treated as "the way real Angular apps are built" — enough that teams scaffold it into a new project before writing a single feature, the same way gets installed before the first line of business logic. That's backwards. The decision tree doesn't ask "is this an enterprise Angular app?" It asks "does this specific piece of state have many writers and a real debuggability need?" Most of it doesn't. A sidebar's open flag, an active tab, a form's draft values — none of that needs an action type and a reducer case. It needs a signal, or at most a signal-holding service. Reaching for NgRx for state like this isn't caution. It's ceremony mistaken for rigor. The Trade-off: Is NgRx-by-Default Actually Safer? Scaffolding NgRx on day one feels like the disciplined, forward-looking choice for a serious Angular app. Whether that discipline is actually being spent where it matters is worth checking. The pain, concretely v1: A new Angular app starts, and NgRx is installed immediately — actions, reducers, effects, selectors — because that's the expected shape of a "real" Angular codebase. v2: The first features need only a handful of trivial UI flags — sidebar open, active tab, a filter string. Each one requires a new action type, a reducer case, and a selector: a five-file change for a single boolean. v3: A genuinely complex feature arrives — a multi-step wizard with real cross-cutting state, undo, and an audit trail. By now the team is fatigued by NgRx ceremony for trivial values, and under-invests in modeling this feature properly, because "state management" has quietly become synonymous with "tedious boilerplate" rather than a tool reached for when it's earned. v4: A new hire spends the first two weeks learning NgRx conventions before shipping a single toggle — time that could have gone toward the actual feature they were hired to build. Side-by-side NgRx by default Signal-holding service by need --- --- --- Adding a trivial UI flag New action, reducer case, selector — several files A new signal in a service, or the component itself Onboarding a new hire Must learn NgRx conventions before shipping anything Reads one service to understand one piece of shared state A genuinely complex, multi-writer feature NgRx earns its cost here — this is what it's for Would need hand-rolled discipline a service can't provide alone Ceremony-to-value ratio for most real features Low — most state isn't this complex High — the tool matches the actual need The signal to switch The moment a signal-holding service can't provide the debuggability — action history, time-travel — or the complex, multi-step orchestration a feature genuinely needs, that's the trigger for NgRx. "This is an enterprise Angular app" is not that signal. A real need for traceable, many-writer state is. Start simple Default to a signal-holding service for shared state. Reach for NgRx only when a feature's update logic is complex enough, and has enough independent writers, that debugging it without devtools would genuinely be painful. The rule Match Angular's state tool to the level, not to convention: signals for local and lifted state, a service for shared cross-cutting state, NgRx only for genuinely complex multi-writer state that needs devtools, and resour…