+State – where domain logic lives
The Infrastructure article turned a raw "ACTIVE" into our own CustomerStatus.Active.
And that is as far as it went.
We still do not know whether this customer is allowed to place an order. We do not know which actions are currently available, which state transition is valid, or which selection matters for a particular domain operation.
This is where +State begins.
Infrastructure translates the outside world into a controlled representation of our own. +State relates those values to each other and interprets them in the context of our domain.
A store is more than a data container
Section titled “A store is more than a data container”At first glance, a +State often looks harmless:
type CustomerState = { customers: Customer[]; selectedCustomerId?: CustomerId; loading: boolean;};On its own, this state merely contains values. It becomes interesting through the questions we can answer with those values:
Which customer is selected?Is the selection still valid?Which customers match a particular condition?Which actions are currently allowed?What follows from a state transition?Not every one of these questions is automatically a business rule. Nor is every field in a store necessarily domain state. A loading flag, for example, remains technical runtime state – even though, as the note Loading Isn’t a Boolean shows, it is rarely a simple switch.
The important difference is that +State does not stop at storing values. This is where state transitions, derived state, and domain decisions emerge from the current state of the application.
State gains meaning through its relationships, rules, and derivations.

A store is not a client-side cache
Section titled “A store is not a client-side cache”A very simple interpretation of state management looks like this:
API ↓Store caches response ↓Component reads responseThat can be perfectly sufficient. Architecturally, however, the store in this model is little more than a cache for backend data. It keeps values around without giving them any meaning of its own.
Our understanding of +State goes further:
Infrastructure ↓ +State ├── owns state ├── processes domain intents ├── applies business rules ├── models state transitions └── produces derived stateWhether this is implemented with Signals, NgRx, Redux, Zustand, RxJS, or another state-management approach does not change that responsibility.
The library decides how state is managed. The architecture decides which responsibilities that state carries.
Business rules live here
Section titled “Business rules live here”Infrastructure translated:
"ACTIVE" ↓CustomerStatus.Active+State can now evaluate that state together with additional information:
CustomerStatus.Active+CreditHold.None+Account.enabled ↓ canOrderWhether a customer is allowed to place an order is no longer a technical translation. It is a rule of our system.
The same applies to questions such as:
- Can an order be cancelled?
- Can a record be deleted?
- Is this state transition valid?
- Which action is available in the current domain situation?
- Does an entity satisfy a particular domain condition?
A business rule does not become presentation logic just because its result eventually disables a button.
And yet this is precisely where domain rules often creep into the UI:
disabled = customer.status !== CustomerStatus.Active || customer.creditHold !== CreditHold.None;As long as there is only one user interface, this can appear harmless.
Once the same decision is needed in an Ionic presentation, a mobile interface, or somewhere else, multiple implementations of the same rule exist.
The domain state can expose a single statement instead:
canOrder;The Presentation then decides how to represent that state: as a disabled button, a greyed-out action, or a menu item that is not shown at all.
It does not decide again whether the action is allowed.
Domain state and derived state
Section titled “Domain state and derived state”Not every piece of information has to be stored separately.
From:
customers+selectedCustomerId ↓selectedCustomerwe can derive selectedCustomer.
The same applies, for example, to:
customers+filter+sort ↓visibleCustomersselectedCustomer and visibleCustomers do not have to be stored as additional copies of their source state and then manually synchronized after every change.
They can emerge from the state that already exists.
This avoids duplicate sources of truth and the synchronization problems that come with them. As soon as two stored values represent the same information, something has to keep both permanently aligned. A derived value does not require that synchronization.

The article Reactive filtering and sorting explores the distinction between query state and derived state in more detail, including why filters and sorting can themselves become part of the state.
Selection can be part of the state
Section titled “Selection can be part of the state”A selection does not require storing an entire entity a second time either.
entities+selectedId ↓selectedEntityIf the selection matters to the domain or to application-wide behavior, the ID may be sufficient as source state. The selected entity can then be derived from it.
Whether a selection belongs in +State at all depends on what it means. A table row that is highlighted for a moment can be pure Presentation state. A selection that drives subsequent domain operations has a different lifetime and responsibility.
Select by ID shows how such a selection flow can be structured in practice.
Multiple sources can form a projection
Section titled “Multiple sources can form a projection”Derived state does not have to come from a single source.
articles+authors+categories ↓projectionSeveral state sources can together form a consumable projection.
What matters is less how many stores or sources are involved and more where responsibility for the resulting information belongs.
ViewModel from multiple data sources shows how several reactive sources can work together. Event-driven Projection covers projections that are built and updated through events.
Both illustrate the same underlying idea: consumers do not necessarily need to know the raw structure of the state underneath.
Not everything belongs in one store
Section titled “Not everything belongs in one store”None of this implies that a feature should have exactly one large +State.
Store boundaries are not primarily determined by file size or by several states happening to use the same entity type.
More useful questions are:
- Which responsibilities belong together?
- Do these states have the same lifecycle?
- Do they change for the same reasons?
- Who owns them?
- Which consumers use them?
Two states can both work with Customer and still have completely different responsibilities.
Conversely, several data types can together form one coherent domain state.
When the read store gets too large discusses these boundaries in detail.
For +State, the important point is this: the layer describes a kind of responsibility, not a requirement to put everything into one store.
Domain changes and intents
Section titled “Domain changes and intents”+State does not only own readable state. This is also where domain-level change requests meet the current state.
deleteRequested ↓current state ↓allowed / rejected ↓state changeOr:
completeOrder ↓Business Rule ↓State TransitionAn intent initially describes an intention.
“This customer should be deleted” is different from “This customer has been deleted.”
Whether the requested change is allowed, and which domain state results from it, depends on the rules and the current state.
The CRUD Slices series describes in detail how Create, Retrieve, Update, and Delete flows can be structured around those responsibilities.
The important boundary here is simpler: a domain-level change request should not first acquire its meaning inside a component or an HTTP service.
Where ViewModels come in
Section titled “Where ViewModels come in”+State can provide state and derived values that later become part of a ViewModel.
That does not mean every view-specific projection has to be fully assembled inside the store.
A projection can make domain state consumable. The Application layer can then combine several such pieces of information for a concrete view or expose a suitably shaped API.
Where exactly that boundary belongs depends on the particular slice.
ViewModel Aggregation discusses in more detail how ViewModels can be composed from several data sources without leaking backend structures or internal state details into the Presentation.
For +State, the important point is that business rules and domain-relevant derivations should not first appear while building a specific user interface.
What +State should and should not do
Section titled “What +State should and should not do”+State can and should, for example:
- own domain-relevant runtime state
- apply business rules
- model domain state transitions
- process domain intents
- produce derived state
- expose relevant selections
- produce domain filters and projections
- react to domain-relevant events
- centralize rules that apply independently of the concrete Presentation
+State should not:
- implement HTTP requests itself
- parse DTOs
- let foreign API contracts leak into domain logic
- use
HttpClient,fetch, or SDK-specific APIs - interpret technical API error codes
- control the router directly
- open dialogs
- show snackbars or toasts
- know DOM or UI-framework details
- distinguish between Angular Material, Ionic, or Bootstrap
- perform concrete view orchestration that belongs in the Application layer
For edge cases, one simple question is surprisingly useful:
Would this rule still apply if Material were replaced by Ionic tomorrow?
If the answer is yes, it is a good candidate for +State.
That is not a definition, but it is a useful rule of thumb.
One domain, multiple user interfaces
Section titled “One domain, multiple user interfaces”The advantages of this boundary become particularly visible when several user interfaces share the same domain logic.
In one long-running project, Angular Material and Ionic were used in parallel for years on top of the same domain foundation.
That worked because business rules were not implemented separately in both Presentations.
+State / \ / \ Material IonicThe Presentation decides how something is rendered. +State provides the domain decisions.
If canOrder had been calculated separately in both Presentations, two implementations would have had to remain synchronized indefinitely. Sooner or later, the two interfaces could have produced different answers to the same domain question.

The same separation creates several other benefits.
Business rules become easier to find. A domain decision spread across components, pipes, form validators, services, and templates can no longer be understood in one place. It has to be reconstructed.
If you know that canOrder originates in the domain state, you also know where to look for the rule.
Derived state remains consistent because multiple copies of the same information do not have to be synchronized manually.
And business rules can be tested independently of a concrete user interface. Whether Material renders a button or Ionic renders a menu item is irrelevant to the canOrder decision.
These advantages do not come from the state-management library.
They come from the boundary.
Not every state belongs in +State
Section titled “Not every state belongs in +State”Giving domain logic a clear home does not mean moving every variable into a store.
Values such as:
isDialogOpenhoveredRowactiveTabtooltipVisibleaccordionExpandedare often Presentation state. They describe how a particular interface is currently being displayed.
Even this boundary is not always absolute. An activeTab, for example, might eventually represent a domain-relevant selection. The variable name tells us very little by itself.
A more useful question is:
Does this state have meaning independently of how it is currently presented?
If the answer is no, +State is probably not its natural home.
The boundary to Application
Section titled “The boundary to Application”+State owns domain-relevant state, business rules, derivations, selections, state transitions, and domain intents.
A concrete view still should not have to know the entire state.
A customer-list view might only need:
customersloadingselectCustomer()createCustomer()A detail view needs a different slice of the same domain logic.
This creates the next boundary of responsibility: +State owns and interprets the domain. Application shapes an API from that domain for a concrete use case or view.
The domain logic remains in one place without forcing every Presentation to understand its entire internal structure.