NgRx SignalStore Patterns
Quick Guide: Use NgRx SignalStore for reactive client state in Angular 17+. Compose stores with
withState,withComputed,withMethods. UsepatchStatefor immutable updates. UsewithEntitiesfor collections. NEVER use traditional NgRx patterns (actions, reducers, effects) in new SignalStore code.
Detailed Resources:
- examples/core.md - signalStore, withState, withComputed, withMethods, withProps
- examples/entities.md - withEntities, CRUD operations, prependEntity/upsertEntity (v20+)
- examples/effects.md - rxMethod, signalMethod (v19+), side effects
- examples/features.md - signalStoreFeature, custom features, DevTools
- examples/testing.md - Unit tests, unprotected(), mocking strategies
- examples/migration.md - Migration from traditional NgRx
- reference.md - Decision frameworks, anti-patterns, red flags
<critical_requirements>
CRITICAL: Before Managing State with NgRx SignalStore
(You MUST use patchState() for ALL state updates - NEVER mutate state directly)
(You MUST wrap async operations in rxMethod() from @ngrx/signals/rxjs-interop for RxJS integration)
(You MUST use withEntities() from @ngrx/signals/entities for entity collections - NOT arrays in state)
(You MUST use named exports ONLY - NO default exports in any store files)
(You MUST use named constants for ALL numbers - NO magic numbers in state code)
</critical_requirements>
Auto-detection: NgRx SignalStore, signalStore, withState, withComputed, withMethods, patchState, withEntities, rxMethod, signalStoreFeature, @ngrx/signals
When to use:
- Managing reactive client state in Angular 17+ applications
- Building composable, reusable store features
- Handling entity collections with CRUD operations
- Integrating RxJS operators for side effects
- Applications requiring fine-grained reactivity with Angular Signals
Key patterns covered:
- Store creation with
signalStore()andprovidedInoptions - State, computed, and methods composition
- Entity management with
withEntities - RxJS integration with
rxMethodand signal-onlysignalMethod - Custom features with
signalStoreFeatureand context-awarewithFeature(v20+) - Derived reactive state with
withLinkedState(v20+) - Call state patterns (loading, loaded, error)
When NOT to use:
- Server/API data as primary source (use HTTP services with rxMethod for caching)
- Simple component-local state (use Angular signals directly)
- Projects still on Angular < 17 (requires signals)
- Teams unfamiliar with functional composition patterns
<philosophy>
Philosophy
NgRx SignalStore is a lightweight, functional state management solution built on Angular Signals. It replaces traditional NgRx patterns (actions, reducers, effects, selectors) with a composable, feature-based approach that eliminates boilerplate while maintaining predictability.
Core Principles:
- Functional Composition - Build stores by composing features like
withState,withComputed,withMethods - Signal-Based Reactivity - Leverage Angular's fine-grained reactivity for optimal performance
- Immutable Updates - Use
patchState()for predictable state transitions - Extensibility - Create custom features with
signalStoreFeature()for reuse across stores
Key Architecture Decisions:
signalStore()creates a fully typed store as an injectable Angular servicepatchState()ensures immutable updates without Immer dependencywithEntities()provides standardized entity management (normalizedids/entityMapstructure with built-in CRUD updaters)rxMethod()bridges Angular Signals with RxJS for complex async flows- Protected state (v18+) prevents external mutations by default; v19+ applies deep freeze recursively
State Ownership:
| State Type | Solution | Reason |
|---|---|---|
| Server/API data | HTTP services + rxMethod | Caching in store, fetch via services |
| Shared client state | SignalStore | Reactivity, composition, DevTools |
| Component-local state | Angular signals | Simpler, no overhead |
| URL state (filters) | Router query params | Shareable, bookmarkable |
<patterns>
Core Patterns
Pattern 1: Basic Store with signalStore
Create stores using signalStore() with withState, withComputed, and withMethods.
Feature Ordering
Features execute in order. State features must come first:
withState()- Define statewithComputed()- Derived values from statewithMethods()- Actions that update statewithHooks()- Lifecycle hooks (onInit, onDestroy)
For implementation examples, see examples/core.md.
Pattern 2: State Updates with patchState
Use patchState() for all state modifications. It ensures immutability and proper signal notifications.
When to Use
- Synchronous state updates
- Partial state updates (spread not needed)
- Inside
withMethods()and custom features
Key Behaviors
- Accepts partial state object or updater functions
- Multiple updaters can be passed to single call
- Works with entity updaters from
@ngrx/signals/entities
For implementation examples, see examples/core.md.
Pattern 3: Entity Management with withEntities
Use withEntities() for collections of items with IDs. Provides standardized CRUD operations and efficient lookups.
When to Use
- Lists of items with unique identifiers
- CRUD operations on collections
- Need for efficient ID-based lookups
- Multiple entity collections in one store
Available Updaters
setAllEntities()- Replace all entitiesaddEntity()/addEntities()- Add new entitiessetEntity()/setEntities()- Upsert entitiesupdateEntity()/updateEntities()- Partial updatesremoveEntity()/removeEntities()- Delete entities
For implementation examples, see examples/entities.md.
Pattern 4: RxJS Integration with rxMethod
Use rxMethod() for side effects that need RxJS operators (debounce, switchMap, etc.).
When to Use
- Debounced search/filtering
- Cancellable HTTP requests
- Complex async flows with multiple operators
- Reactive streams from signals
Key Behaviors
- Accepts
Observable<T>,Signal<T>, orTas input - Factory function receives
Observable<T>for piping - Runs in injection context (can use
inject()) - Auto-unsubscribes on store destroy
For implementation examples, see examples/effects.md.
Pattern 4b: Signal-Only Side Effects with signalMethod (v19+)
Use signalMethod() for side effects that don't need RxJS operators.
When to Use
- Simple side effects without RxJS dependency
- When you want to work purely with Signals
- Side effects that don't require debounce/switchMap/cancellation
- When injection context is not available
Key Behaviors
- Accepts
Signal<T>orTas input (no Observable) - Processor function runs independently of injection context
- Only parameter signals are tracked; internal signals remain untracked
- You must handle race conditions manually (no built-in switchMap)
rxMethod vs signalMethod
| Feature | rxMethod | signalMethod |
|---|---|---|
| RxJS required | Yes | No |
| Operators (debounce, switchMap) | Yes | No |
| Race condition handling | B |