NGrx状态更新时所有Observable是否触发?含Entity行为疑问
Great question—this is a common point of confusion when working with immutable state in NgRx, especially when debating state structure. Let’s break this down step by step.
Core Question: Do All Observables Trigger on State Updates?
First, let’s confirm the basics: NgRx state is immutable, so every update creates a new root state object. But not all observables will emit a new value—it depends on how you select state and whether you use memoized selectors.
Using your example state:
interface State { backendState: BackendData; frontendState: FrontendState; }
When you update frontendState and generate a new root state:
- Subscriptions to the full root state (e.g.,
store.select(state => state)): Will trigger, since the root state reference is entirely new. - Subscriptions to
frontendState:- Basic projection (e.g.,
store.select(state => state.frontendState)): Will trigger, because the new root state runs the projection, which returns the newfrontendStatereference. - Memoized selector (created with
createSelector): Will also trigger, since the input slice (frontendState) has changed.
- Basic projection (e.g.,
- Subscriptions to
backendState:- Basic projection: Won’t trigger. NgRx’s
selectoperator usesdistinctUntilChangedunder the hood, which compares values by reference. SincebackendStatehasn’t changed (same reference), the observable filters out the duplicate value. - Memoized selector: Won’t trigger either.
createSelectorcaches results and only recalculates when its input selectors return new values. SincebackendStateis unchanged, the selector returns the cached result, so no emission occurs.
- Basic projection: Won’t trigger. NgRx’s
Flat vs. Nested State: Is There a Performance Difference?
Your colleagues’ preference for flat state (using string IDs and mappings) likely stems from a misunderstanding of how memoized selectors work. Here’s the reality:
- Nested state doesn’t cause unnecessary emissions if you use memoized selectors correctly. Only the slices of state you care about will trigger their observables when changed—regardless of whether the state is nested or flat.
- Flat state can help with complex, cross-cutting state, but it’s not inherently more performant. Nested state often leads to cleaner, more maintainable code by grouping related state together (e.g., separating
backendStateandfrontendStatemakes it clear which parts relate to backend vs. frontend logic). - The performance gain from flat state is negligible compared to the benefits of using memoized selectors. Don’t sacrifice code clarity for a hypothetical performance boost.
How Does NgRx Entity Behave?
NgRx Entity uses a structured state format for list data:
interface EntityState<T> { ids: string[]; entities: { [id: string]: T }; }
When you update an entity (e.g., with updateOne):
- A new
entitiesobject is created (immutability requires replacing objects instead of mutating them), but theidsarray keeps its original reference if no entities were added or removed. - Memoized selectors for entities (like
selectEntities) will emit a new value because theentitiesreference changed. - Selectors for
ids(likeselectIds) won’t emit, since the array reference is unchanged. - NgRx Entity’s built-in selectors are already memoized, so you get efficient state selection out of the box—no need to roll your own ID-based mappings.
Best Practices
- Always use memoized selectors (
createSelector) instead of basic projections. They prevent unnecessary emissions and make state selection more efficient, regardless of state structure. - Organize state by logical concern first. Group related state into nested slices if it makes the code more readable—don’t flatten just for the sake of it.
- Use NgRx Entity for list data instead of manual ID-based mappings. It provides optimized CRUD reducers and selectors, reducing boilerplate and ensuring consistent, efficient state updates.
- Avoid subscribing to the entire store. Only select the specific slices of state your component needs—this minimizes the amount of data flowing through your observables.
内容的提问来源于stack exchange,提问作者SchreiberLex

