Implement Angular State Management Patterns
An Angular state management skill covering Signals, NgRx, and Component Store, with a state-category selection guide and migration path.
Why it matters
Master modern Angular state management, from local component state using Signals to global solutions like NgRx and Akita, and server state synchronization.
Outcomes
What it gets done
Set up global state management in Angular applications.
Choose the right state management solution (Signals, NgRx, Akita).
Implement component-level stores and manage shared state.
Debug state-related issues and migrate legacy patterns.
Install
Add it to your toolbox
Run in your project directory:
curl -fsSL https://spark.entire.vc/get/ag-angular-state-management | bash Overview
Angular State Management
An Angular state management skill covering Signals, NgRx Store, Component Store, and server state, with selection criteria by app scale and a BehaviorSubject-to-Signals migration path. Use when choosing or implementing Angular state management; use a separate React state management skill instead if the task isn't Angular-specific.
What it does
This skill guides Angular state management across six state categories, each with its own recommended solution: local component state (Signals/signal()), shared state between related components (signal services), global app-wide state (NgRx, Akita, Elf), server state with caching (NgRx Query, RxAngular), URL state (ActivatedRoute), and form state (Reactive Forms). Selection criteria scale by app size and workload: signal services for small apps with simple state, component stores for medium apps, NgRx Store for large apps with complex state, NgRx Query plus signal services for heavy server interaction, and RxAngular plus Signals for real-time updates. Three signal-based patterns are covered: a simple signal service exposing a private writable signal with a read-only public signal and computed() derived values (e.g. a counter service with doubled and isPositive computed signals); a feature signal store; and NgRx's SignalStore. NgRx Store for global state wires feature reducers into an ActionReducerMap and registers them at bootstrap via provideStore, provideEffects, and provideStoreDevtools; a feature slice built on top of that combines an action group (createActionGroup), a reducer (createReducer with on() handlers), selectors (createFeatureSelector/createSelector), and an effect (createEffect with Actions/ofType/switchMap/catchError) for async side effects like loading a user from an API; components then read that state with store.selectSignal(selector) and dispatch actions with store.dispatch(action). RxJS-based Component Store offers an alternative for local feature state: it extends ComponentStore<State>, exposes selectors via select(), synchronous state updates via updater(), and async side effects via effect() piping tap/switchMap/catchError. A server-state pattern combines HTTP calls with signals, including an optimistic-update technique that applies a change immediately and rolls back the signal state if the request fails.
Five practices are recommended: use Signals for local state (simple, reactive, no manual subscriptions), use computed() for derived data (auto-updating and memoized), colocate state with its feature for maintainability, use NgRx for complex flows needing actions/effects/devtools, and prefer inject() over constructor injection. Five anti-patterns are flagged: storing derived data instead of computing it, mutating signals directly instead of using set()/update(), over-globalizing state that could stay local, mixing RxJS and Signals chaotically instead of choosing one primary model and bridging with toSignal/toObservable, and subscribing manually in components instead of reading signals directly in the template. A migration path shows converting a BehaviorSubject-based service to an equivalent signal-based one, and bridging the two systems with toSignal (Observable to Signal, e.g. converting route params) and toObservable (Signal to Observable, e.g. piping a signal into debounceTime/switchMap for a debounced search).
When to use - and when NOT to
Use this when setting up global state management in Angular, choosing between Signals, NgRx, or Akita, managing component-level stores, implementing optimistic updates, debugging state-related issues, or migrating from a legacy RxJS-based state pattern to Signals.
Do not use this for anything unrelated to Angular state management, and use a react-state-management skill instead if the task is actually about React state.
Inputs and outputs
Input is a state management requirement (local UI state, shared state between components, global app state, server-cached data, or a legacy BehaviorSubject service to migrate). Output is Angular/TypeScript code using the selected pattern - a signal service, an NgRx feature slice, a Component Store, or a signal-to-Observable bridge - matched to the state category and app scale via the selection criteria.
Integrations
Built on Angular Signals (signal(), computed(), inject()), NgRx (Store, SignalStore, Query, Component Store, Effects), Akita/Elf as alternative global-state libraries, RxAngular for real-time server state, and @angular/core/rxjs-interop (toSignal/toObservable) for bridging Signals and RxJS.
Who it's for
Angular developers choosing a state management approach for a new feature or app, or migrating an existing RxJS-based state layer to Signals.
// services/counter.service.ts
import { Injectable, signal, computed } from "@angular/core";
@Injectable({ providedIn: "root" })
export class CounterService {
// Private writable signals
private _count = signal(0);
// Public read-only
readonly count = this._count.asReadonly();
readonly doubled = computed(() => this._count() * 2);
readonly isPositive = computed(() => this._count() > 0);
increment() {
this._count.update((v) => v + 1);
}
decrement() {
this._count.update((v) => v - 1);
}
reset() {
this._count.set(0);
}
}
FAQ
Common questions
Discussion
Questions & comments ยท 0
Sign In Sign in to leave a comment.