← Engineering & Writing

State architecture / Design study

Reactive consistency starts with ownership

A design study of canonical state, derived values, and asynchronous boundaries in reactive interfaces.

A reactive screen can update instantly and still describe an impossible situation. A selected account, its permissions, and its pending request may each be individually valid while belonging to different moments. The useful design question is which owner can guarantee that these values describe the same user intent.

Technical design study: conceptual guidance with illustrative examples and proposed checks, not a description of deployed client architecture, measured production outcomes, or tests already run.

01 / Ownership

Store intent once; derive the rest

Consider an account picker with a detail panel. Store the selected account ID and the loaded account collection. Derive the selected account from those inputs instead of storing a second mutable selected-account object. The invariant is simple: every visible detail belongs to the selected ID, or the screen explicitly shows that its data is unavailable. Refreshing the collection cannot silently leave a detached object on screen.

Ownership also defines lifetime. A route can own the selected ID while a shared repository owns reusable account records. A child component receives values and emits user intent; it does not maintain an independent account selection. The counterexample is two subscribers copying each other through effects. Their apparent synchronization creates ordering questions and makes it unclear which update wins when navigation and refresh happen together.

02 / Derivation

Equality is an observable contract

Angular computed signals are lazy and memoized, and track the signals actually read during their latest derivation. Use that model for synchronous projections such as whether an account can be edited. Keep the derivation free of network requests and writes to other state. A computed value answers a question; it should not also initiate the operation whose result changes the answer.

An equality function decides when two values count as unchanged for reactive consumers. Comparing only an account ID would hide a permission change if permissions are part of the value consumers need. Structural equality may avoid redundant downstream updates, but it does not deduplicate every side effect or provide transaction isolation. Logging, subscriptions, and imperative calls need their own lifecycle and ownership. Define equality around consumer-visible meaning before optimizing allocations.

03 / Async authority

A response needs permission to commit

Suppose the user selects account A and then account B before A finishes loading. Associate each load with the selected ID and a request generation. Commit a result only while both still match the current owner. Model loading, success, and failure together with that identity, so an old error cannot replace a newer success. A blank panel is not the only safe option: retained data can remain visible when clearly labeled as belonging to the previous selection.

RxJS switchMap is useful when a new selection should unsubscribe from the previous inner stream. Unsubscription alone is not a universal promise that remote work stopped. An adapter may ignore cancellation, and a server may already have accepted a write. Keep an identity guard where results enter state, and give mutations their own semantics instead of treating every operation as a replaceable read.

System flow

From selection to an authorized result

  1. 01

    Capture intent

    The route owner records the selected ID and advances its request generation.

  2. 02

    Derive the view

    Pure projections expose matching cached data and an explicit loading state.

  3. 03

    Resolve the read

    A replaceable request returns data or an error tagged with its initiating identity.

  4. 04

    Commit conditionally

    The owner accepts only the current generation, then dependent values recompute.

Decision record

Design decisions and tradeoffs

↔ Scroll horizontally to compare approaches and tradeoffs.

Approaches and their tradeoffs
DecisionApproachTradeoff
One selection ownerKeep the selected ID in the route state.Children need explicit inputs and intent events, but navigation has one authoritative meaning.
Derived permissionsCompute editability from current account and permission data.Missing inputs must be represented explicitly; copying a boolean would conceal that uncertainty.
Guarded replacement readsCombine stream replacement with a commit-time identity check.A small amount of request metadata remains necessary even when cancellation usually works.

Failure cases & validation

Failure scenarios to validate

Proposed validation scenarios and expected behavior. These are design checks, not reported production test results.

  1. A resolves after the user has selected B.

    A cannot replace B’s data, error, or loading state.

  2. Permissions change while the account ID stays the same.

    Editability updates; an equality shortcut must not suppress the change.

  3. A refreshed collection no longer contains the selection.

    The detail view shows an explicit unavailable state instead of an orphaned object.

  4. The route closes with a read in flight.

    Owned subscriptions are disposed and a late callback cannot commit to the destroyed owner.

  5. The same selected account is refreshed twice.

    Only the current request generation can settle the loading and error state.

Reflection

Engineering lessons

  • 01

    Write the consistency invariant before choosing a signal, stream, or store.

  • 02

    Use derivation for relationships and explicit ownership for effects.

  • 03

    Treat cancellation as resource management and commit authority as a separate correctness rule.

Evidence & further reading

Technical references