State management or not?
A modern frontend application can’t get by without state management anymore.
That’s a bold claim. Absolute, provocative, and of course too sweeping. A static landing page doesn’t need Redux. Neither does a form with three fields. And anyone who introduces a global store for every button click probably isn’t solving an architecture problem — they’re creating one.
Still, the claim is a useful hook, because it makes the real discussion visible.
The decisive question isn’t:
Do we need state management?
It’s:
Where does our state live, who’s allowed to change it, and how easy is it to trace as it flows through our application?
Because state always exists. The only question is whether we model it deliberately — or whether it randomly spreads itself across components, services, subscriptions, input/output chains, router parameters, and local variables.

What actually is state management?
Section titled “What actually is state management?”State management doesn’t automatically mean Redux, NgRx, Signals, stores, or any particular library.
At its core, state management just means:
The state of an application is managed deliberately.
That includes questions like:
- What data does the application currently know about?
- Where does that data get held?
- Who’s allowed to change it?
- How do changes get triggered?
- How do other parts of the application react to it?
- How does raw data turn into a UI model?
- How do we handle loading, error, success, and side effects?
An application without deliberate state management still has state. It’s just that the state ends up scattered somewhere.
A little in the component.
A little in the service.
A little in the router.
A little in a Subject.
A little in a subscription.
A little in the template.
A little in the hope that nobody clicks things in the wrong order.
That’s state management too. Just implicit.

What problem does state management solve?
Section titled “What problem does state management solve?”State management doesn’t solve the problem that applications are complex.
It solves a different problem:
It makes complexity visible, easier to locate, and testable.
In many frontend applications, the underlying workflow isn’t the real problem. It’s often fairly manageable:
- load a record
- open a form
- save a change
- show success
- update the list
- navigate to the detail page
- show an error
The problem is that these steps get scattered all over the application.
The component calls the service.
The service holds a Subject.
Another component subscribes to it.
The toast fires on the side.
Routing happens in a callback.
The reload hangs off another callback.
And eventually nobody knows anymore whether an error comes from the HTTP call, the mapping, the template, or some old subscription.
State management brings order into this.
Not because a store magically produces better code. But because it forces the application to think in terms of flows.
The real benefit: clear flows
Section titled “The real benefit: clear flows”A good state management approach separates at least these things:
- Intent: what does the user or the system want to do?
- Command/event: what gets sent into the application?
- Operation: what external action happens, e.g. HTTP?
- Result: what business outcome follows?
- State update: how does state change?
- View model: what does the UI get to see?
- Side effects: toast, navigation, reload, tracking
That produces a traceable flow:
Button click -> createIntent(payload) -> event.on(createIntent) -> http.put(payload) -> createSuccess(result) or createError(error) -> independent reactions
The button doesn’t start the whole process. It just expresses an intent.
The UI says:
I want to create something.
But it doesn’t decide for itself which infrastructure gets called, which data gets reloaded, which toast appears, or where navigation goes. Those reactions belong in the application flow, not in the button’s click handler.
This is exactly where the architectural benefit comes from.
Not a callback chain — an event flow
Section titled “Not a callback chain — an event flow”A common fallacy is building success reactions as a linear chain.
Something like this:
save() -> http.put() -> reload() -> showToast() -> navigate()That works. But it couples things together more tightly than necessary.
It’s often better to do this:
createSuccess(result) ├─ event.on(createSuccess) -> reload resource ├─ event.on(createSuccess) -> show toast └─ event.on(createSuccess) -> navigateThe difference looks small but is architecturally big.
In the first case, everything hangs off one imperative chain. In the second case, there’s a business outcome that multiple parts of the application can react to independently.

That makes extending things easier.
If an analytics event needs to be sent later too, you don’t need to dig into the existing save flow to add it. You just add another reaction to createSuccess.
”When do you need state management?”
Section titled “”When do you need state management?””This question comes up a lot. And it’s a fair one.
The pragmatic answer is:
As soon as state gets read or changed from more than one place, state management pays off very quickly.
Typical signals are:
- Multiple components need the same data.
- Data needs to update consistently after actions.
- Loading/error/success states keep repeating.
- After a save, toast, reload, and navigation all have to happen.
- Components keep getting bigger.
- Services suddenly contain business logic, UI logic, and cache logic all at once.
- There are a lot of manual subscriptions.
- Debugging consists of “Where did this get set?”
- Tests are hard because logic is stuck to components.
The stricter, more disciplined answer would be even blunter:
If you want to learn reactive programming, always use state management.
Not because every application strictly needs it. But because it trains you to think cleanly:
- Data flows in one direction.
- Changes happen through explicit events.
- UI is derived from state.
- Side effects are modeled deliberately.
- Components become consumers, not control centers.
That’s more structure at the start. It’s less chaos later.

The counter-question from a training session
Section titled “The counter-question from a training session”I once started a training session with a simple question:
Do you want to program reactively in this project?
If the answer is “no,” we don’t need to talk about Redux, stores, or state management. Then we can keep working imperatively and accept the consequences.
But if the answer is “yes,” then we need to talk about state management.
Because reactive programming doesn’t mean using an Observable or a signal() somewhere. An application only becomes reactive once UI, data, and events get deliberately modeled as flows.
Three implementations, the same workflow
Section titled “Three implementations, the same workflow”To illustrate this, I implemented the same CRUD flow three times:
- An observer service solution
- NgRx with the classic Redux pattern
- NgRx Signals
The workflow was identical:
- create a record
- execute an HTTP request
- handle success or failure
- update the list
- show a success toast
- navigate programmatically
- cleanly separate the UI from infrastructure and application logic
In every variant, the layers were deliberately kept separate:
- infrastructure for HTTP
- an application layer for use cases
- a state layer for state and flows
- UI purely as a consumer
The interesting part wasn’t that one library “won.”
The interesting part was what the participants noticed:
The workflow stayed the same, but the code got smaller.
Not because state management always means less code. That would be too simple. Classic Redux can produce a lot of boilerplate too.
But the better the tool fits the paradigm, the less you have to invent yourself.

Observer service: it works, but you build a lot yourself
Section titled “Observer service: it works, but you build a lot yourself”An observer service can work for small to medium cases.
You have a service, with a Subject inside it, maybe a BehaviorSubject, methods for actions, and public streams for the UI.
That’s not wrong.
The problem is: you’re building your own little state management system.
And along with it, your own rules:
- How do you name events?
- Where does error handling happen?
- Where does loading happen?
- Who updates the cache?
- Who triggers reloads?
- Who’s allowed to call
next()? - Where do side effects get triggered?
- How do you test this consistently?
If the team is very disciplined, this can work. But discipline scales worse than structure.
Classic NgRx: a lot of structure, but a lot of ceremony too
Section titled “Classic NgRx: a lot of structure, but a lot of ceremony too”NgRx in the Redux style comes with very clear rules:
- Actions describe events.
- Reducers change state.
- Effects handle side effects.
- Selectors prepare data for the UI.
That’s architecturally strong. Especially in larger applications.
The price is ceremony.
You write more files, more types, more wiring code. For teams that understand Redux, that’s an advantage. For teams that just want to “quickly load some data,” it often feels heavy.
The payoff shows up once the application is big enough that this structure saves more than it costs.
NgRx Signals: less code, same idea
Section titled “NgRx Signals: less code, same idea”NgRx Signals doesn’t change the basic idea of state management.
It just brings it closer to Angular.
State, computed values, methods, and reactive derivations sit closer together. A lot of things that used to be spread across actions, reducers, effects, and selectors can now be expressed closer to the business model.
That can make the same CRUD flow noticeably smaller.
Not because the architecture disappears.
But because less infrastructure code is needed to express it.
The UI stays a consumer. HTTP stays infrastructure. Business operations stay clearly modeled. State stays explicit.
It’s just the path there that’s less heavyweight.
What new problem does state management introduce?
Section titled “What new problem does state management introduce?”State management isn’t a free win.
It brings new questions along with it:
- Where does local component state end?
- What belongs in the store?
- How granular should stores be?
- How do you prevent global dumping grounds?
- How do you name events and methods consistently?
- How do you separate command state from read state?
- How do you prevent the store from becoming the new god class?
Bad state management is just another form of bad code.
A global store where everything lives isn’t an architecture concept. It’s a basement.

State management only helps when it’s combined with clear boundaries:
- feature stores instead of a global data dump
- view models instead of template logic
- clear events/intents instead of arbitrary methods
- deliberately modeling side effects
- keeping components small
- not mixing infrastructure with UI
- derivations instead of manual synchronization
Less code isn’t the most important point
Section titled “Less code isn’t the most important point”Less code is nice. But it’s not the most important benefit.
The most important benefit is:
You know where to look.
If a save fails, I look at the command flow. If data displays wrong, I look at the view model. If a toast is missing, I look at the success handler. If a reload doesn’t happen, I look at the reaction to the success event. If the UI is acting up, I first check whether it even contains its own logic.
That improves debugging enormously.
And it improves tests.
Because once the component only consumes state and sends intents, I have less UI to test. The actual logic lives in functions, stores, mappers, and use cases. That’s easier, faster, and more reliable to test.

The real decision
Section titled “The real decision”The question “state management or not?” is often too blunt.
These questions work better:
- How complex are our state flows?
- How many components share data?
- How often do actions need to trigger follow-up reactions?
- How important is debugging traceability?
- How testable does the business logic need to be?
- How well does the team understand reactive programming?
- How big is the risk that components turn into control centers?
In modern frontend applications, the answer is often no longer:
We don’t need state management.
It’s more like:
We don’t need the same level of state management everywhere.
Local UI state is allowed to stay local.
Whether a dropdown is open doesn’t need to go in a global store. Whether a dialog is currently visible, maybe not either. Whether a form field is focused, very probably not.
But business state, loaded data, command flows, permissions, selections, filters, loading states, and derived view models almost always benefit from explicit structure.
Conclusion
Section titled “Conclusion”A modern frontend application can’t get by without state management anymore.
As an absolute statement, that’s wrong.
As an architectural warning, it’s pretty close to the truth.
Because state always exists. The only decision is whether we manage it deliberately or let the application sort it out as it grows.
Good state management doesn’t mean:
We use a library.
Good state management means:
- clear data flows
- clear events
- clear ownership
- small UI components
- better tests
- better debugging
- less implicit coupling
- less accidental architecture
So the better question isn’t:
At what point do we need state management?
It’s:
At what point can we afford not to have it?