From web-state-ngrx-signalstore
NgRx SignalStore patterns for Angular state management. Use when managing client state with Angular Signals, composing store features, handling entities, or integrating RxJS effects.
How this skill is triggered — by the user, by Claude, or both
Slash command
/web-state-ngrx-signalstore:web-state-ngrx-signalstoreThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
> **Quick Guide:** Use NgRx SignalStore for reactive client state in Angular 17+. Compose stores with `withState`, `withComputed`, `withMethods`. Use `patchState` for immutable updates. Use `withEntities` for collections. NEVER use traditional NgRx patterns (actions, reducers, effects) in new SignalStore code.
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:
<critical_requirements>
(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:
Key patterns covered:
signalStore() and providedIn optionswithEntitiesrxMethod and signal-only signalMethodsignalStoreFeature and context-aware withFeature (v20+)withLinkedState (v20+)When NOT to use:
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:
withState, withComputed, withMethodspatchState() for predictable state transitionssignalStoreFeature() for reuse across storesKey Architecture Decisions:
signalStore() creates a fully typed store as an injectable Angular servicepatchState() ensures immutable updates without Immer dependencywithEntities() provides standardized entity management (normalized ids/entityMap structure with built-in CRUD updaters)rxMethod() bridges Angular Signals with RxJS for complex async flowsState 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 |
Create stores using signalStore() with withState, withComputed, and withMethods.
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.
Use patchState() for all state modifications. It ensures immutability and proper signal notifications.
withMethods() and custom features@ngrx/signals/entitiesFor implementation examples, see examples/core.md.
Use withEntities() for collections of items with IDs. Provides standardized CRUD operations and efficient lookups.
setAllEntities() - Replace all entitiesaddEntity() / addEntities() - Add new entitiessetEntity() / setEntities() - Upsert entitiesupdateEntity() / updateEntities() - Partial updatesremoveEntity() / removeEntities() - Delete entitiesFor implementation examples, see examples/entities.md.
Use rxMethod() for side effects that need RxJS operators (debounce, switchMap, etc.).
Observable<T>, Signal<T>, or T as inputObservable<T> for pipinginject())For implementation examples, see examples/effects.md.
Use signalMethod() for side effects that don't need RxJS operators.
Signal<T> or T as input (no Observable)| Feature | rxMethod | signalMethod |
|---|---|---|
| RxJS required | Yes | No |
| Operators (debounce, switchMap) | Yes | No |
| Race condition handling | Built-in | Manual |
| Input types | T, Signal, Observable | T, Signal |
For implementation examples, see examples/effects.md.
Use signalStoreFeature() to create reusable store functionality.
Use type() helper to specify required store members:
For implementation examples, see examples/features.md.
Use withHooks() for initialization and cleanup logic.
onInit() - Called when store is instantiatedonDestroy() - Called when store is destroyedFor implementation examples, see examples/core.md.
Track loading, loaded, and error states for async operations using withCallState() from ngrx-toolkit or custom implementation.
loading - Operation in progressloaded - Operation completed successfullyerror - Operation failed with errorFor implementation examples, see examples/features.md.
Use withFeature() to create features that have access to the current store's methods and properties. Unlike signalStoreFeature(), withFeature receives the store instance, enabling reusable features that can call store-specific methods.
signalStoreFeature() is insufficient because the feature needs store contextimport { withFeature } from "@ngrx/signals";
// withFeature receives the store instance
export const ProductStore = signalStore(
{ providedIn: "root" },
withMethods((store) => ({
load: rxMethod<string>(/* fetch logic */),
})),
withFeature((store) =>
withEntityLoader((id) => firstValueFrom(store.load(id))),
),
);
Use withLinkedState() to create state signals that are automatically recomputed when their source signals change. Unlike withComputed(), linked state is writable and can be overridden.
import { withLinkedState } from "@ngrx/signals";
export const FilterStore = signalStore(
withState({ items: [] as Item[] }),
withLinkedState(({ items }) => ({
// Recomputes when items change, but can also be set independently
selectedId: () => items()[0]?.id ?? null,
})),
);
Angular DI Integration:
SignalStore is an Angular service. Use { providedIn: 'root' } for singletons or component-level providers: [Store] for scoped instances that are destroyed with the component.
See examples/core.md Pattern 5 and Component-Level Stores for DI patterns.
DevTools Integration:
Use withDevtools('name') from @angular-architects/ngrx-toolkit for Redux DevTools support. Place it before withState in the feature chain.
See examples/features.md Pattern 5 for setup.
Testing Integration:
unprotected() from @ngrx/signals/testing to bypass state protection for test setupTestBed.configureTestingModule() with mocked servicesSee examples/testing.md for patterns.
<red_flags>
High Priority Issues:
patchState - breaks reactivity and signal notificationswithEntities for entity collections - loses normalized state, O(1) lookupsrxMethod - loses cancellation and proper cleanupMedium Priority Issues:
rxMethod pipelinesentityConfig() for custom entity IDs (v18+)signalMethod for async operations with race conditions (use rxMethod with switchMap)Gotchas & Edge Cases:
withHooks.onInit() runs during store instantiation - be careful with side effects in testspatchState with entity updaters requires collection option when using named collectionsrxMethod auto-unsubscribes on destroy - do not manually unsubscribepatchState calls by defaultObject.freeze in dev mode - use withProps() for mutable objects like FormGroupSee reference.md for detailed anti-patterns with code examples.
</red_flags>
<critical_reminders>
(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)
Failure to follow these rules will cause reactivity bugs, state corruption, and inconsistent behavior.
</critical_reminders>
npx claudepluginhub agents-inc/skills --plugin web-state-ngrx-signalstoreImplements NgRx Store (Redux pattern) for global state or NgRx SignalStore for local, signal-based state in Angular apps. Helps choose the right tool for the complexity level.
Guides Angular state management with Signals, NgRx, RxJS for local, global, server state. Use for setup, component stores, solution selection, debugging, migrations.
Guides Angular Signals usage for fine-grained reactive state management and zone-less change detection in Angular 16+ applications.