Skip to main content

angular-state-management

Master modern Angular state management with Signals, NgRx, and RxJS. Use when setting up global state, managing component stores, choosing between state solutions, or migrating from legacy patterns.

Ir a la instalación

Datos de origen

Repositorio
LongLeo287/OmniClaw
Última actividad en el origen
11 de abril de 2026 a las 09:08
Idioma detectado de SKILL.md
inglés
Estrellas
7
Forks
0

Opciones de instalación

De forma predeterminada está seleccionado el prompt que primero revisa el origen. Puedes cambiar a un comando directo o descargar una copia local.

Revisa los archivos de origen

Lee SKILL.md y los archivos complementarios que muestra SkillsMP antes de decidir si quieres instalarlo.

Explorador de archivos
5 archivos

Mostrando SKILL.md

SKILL.md
Instrucciones de origen · Vista previa de solo lectura
name
angular_state_management
description
Master modern Angular state management with Signals, NgRx, and RxJS. Use when setting up global state, managing component stores, choosing between state solutions, or migrating from legacy patterns.
risk
safe
source
self
date_added
2026-02-27
# Angular State Management Comprehensive guide to modern Angular state management patterns, from Signal-based local state to global stores and server state synchronization. ## When to Use This Skill - Setting up global state management in Angular - Choosing between Signals, NgRx, or Akita - Managing component-level stores - Implementing optimistic updates - Debugging state-related issues - Migrating from legacy state patterns ## Do Not Use This Skill When - The task is unrelated to Angular state management - You need React state management → use `react-state-management` --- ## Core Concepts ### State Categories | Type | Description | Solutions | | ---------------- | ---------------------------- | --------------------- | | **Local State** | Component-specific, UI state | Signals, `signal()` | | **Shared State** | Between related components | Signal services | | **Global State** | App-wide, complex | NgRx, Akita, Elf | | **Server State** | Remote data, caching | NgRx Query, RxAngular | | **URL State** | Route parameters | ActivatedRoute | | **Form State** | Input values, validation | Reactive Forms | ### Selection Criteria ``` Small app, simple state → Signal Services Medium app, moderate state → Component Stores Large app, complex state → NgRx Store Heavy server interaction → NgRx Query + Signal Services Real-time updates → RxAngular + Signals ``` --- ## Quick Start: Signal-Based State ### Pattern 1: Simple Signal Service ```typescript // 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); } } // Usage in component @Component({ template: ` <p>Count: {{ counter.count() }}</p> <p>Doubled: {{ counter.doubled() }}</p> <button (click)="counter.increment()">+</button> `, }) export class CounterComponent { counter = inject(CounterService); } ``` ### Pattern 2: Feature Signal Store ```typescript // stores/user.store.ts import { Injectable, signal, computed, inject } from "@angular/core"; import { HttpClient } from "@angular/common/http"; import { toSignal } from "@angular/core/rxjs-interop"; interface User { id: string; name: string; email: string; } interface UserState { user: User | null; loading: boolean; error: string | null; } @Injectable({ providedIn: "root" }) export class UserStore { private http = inject(HttpClient); // State signals private _user = signal<User | null>(null); private _loading = signal(false); private _error = signal<string | null>(null); // Selectors (read-only computed) readonly user = computed(() => this._user()); readonly loading = computed(() => this._loading()); readonly error = computed(() => this._error()); readonly isAuthenticated = computed(() => this._user() !== null); readonly displayName = computed(() => this._user()?.name ?? "Guest"); // Actions async loadUser(id: string) { this._loading.set(true); this._error.set(null); try { const user = await fetch(`/api/users/${id}`).then((r) => r.json()); this._user.set(user); } catch (e) { this._error.set("Failed to load user"); } finally { this._loading.set(false); } } updateUser(updates: Partial<User>) { this._user.update((user) => (user ? { ...user, ...updates } : null)); } logout() { this._user.set(null); this._error.set(null); } } ``` ### Pattern 3: SignalStore (NgRx Signals) ```typescript // stores/products.store.ts import { signalStore, withState, withMethods, withComputed, patchState, } from "@ngrx/signals"; import { inject } from "@angular/core"; import { ProductService } from "./product.service"; interface ProductState { products: Product[]; loading: boolean; filter: string; } const initialState: ProductState = { products: [], loading: false, filter: "", }; export const ProductStore = signalStore( { providedIn: "root" }, withState(initialState), withComputed((store) => ({ filteredProducts: computed(() => { const filter = store.filter().toLowerCase(); return store .products() .filter((p) => p.name.toLowerCase().includes(filter)); }), totalCount: computed(() => store.products().length), })), withMethods((store, productService = inject(ProductService)) => ({ async loadProducts() { patchState(store, { loading: true }); try { const products = await productService.getAll(); patchState(store, { products, loading: false }); } catch { patchState(store, { loading: false }); } }, setFilter(filter: string) { patchState(store, { filter }); }, addProduct(product: Product) { patchState(store, ({ products }) => ({ products: [...products, product], })); }, })), ); // Usage @Component({ template: ` <input (input)="store.setFilter($event.target.value)" /> @if (store.loading()) { <app-spinner /> } @else { @for (product of store.filteredProducts(); track product.id) { <app-product-card [product]="product" /> } } `, }) export class ProductListComponent { store = inject(ProductStore); ngOnInit() { this.store.loadProducts(); } } ``` --- ## NgRx Store (Global State) ### Setup ```typescript // store/app.state.ts import { ActionReducerMap } from "@ngrx/store"; export interface AppState { user: UserState; cart: CartState; } export const reducers: ActionReducerMap<AppState> = { user: userReducer, cart: cartReducer, }; // main.ts bootstrapApplication(AppComponent, { providers: [ provideStore(reducers), provideEffects([UserEffects, CartEffects]), provideStoreDevtools({ maxAge: 25 }), ], }); ``` ### Feature Slice Pattern ```typescript // store/user/user.actions.ts import { createActionGroup, props, emptyProps } from "@ngrx/store"; export const UserActions = createActionGroup({ source: "User", events: { "Load User": props<{ userId: string }>(), "Load User Success": props<{ user: User }>(), "Load User Failure": props<{ error: string }>(), "Update User": props<{ updates: Partial<User> }>(), Logout: emptyProps(), }, }); ``` ```typescript // store/user/user.reducer.ts import { createReducer, on } from "@ngrx/store"; import { UserActions } from "./user.actions"; export interface UserState { user: User | null; loading: boolean; error: string | null; } const initialState: UserState = { user: null, loading: false, error: null, }; export const userReducer = createReducer( initialState, on(UserActions.loadUser, (state) => ({ ...state, loading: true, error: null, })), on(UserActions.loadUserSuccess, (state, { user }) => ({ ...state, user, loading: false, })), on(UserActions.loadUserFailure, (state, { error }) => ({ ...state, loading: false, error, })), on(UserActions.logout, () => initialState), ); ``` ```typescript // store/user/user.selectors.ts import { createFeatureSelector, createSelector } from "@ngrx/store"; import { UserState } from "./user.reducer"; export const selectUserState = createFeatureSelector<UserState>("user"); export const selectUser = createSelector( selectUserState, (state) => state.user, ); export const selectUserLoading = createSelector( selectUserState, (state) => state.loading, ); export const selectIsAuthenticated = createSelector( selectUser, (user) => user !== null, ); ``` ```typescript // store/user/user.effects.ts import { Injectable, inject } from "@angular/core"; import { Actions, createEffect, ofType } from "@ngrx/effects"; import { switchMap, map, catchError, of } from "rxjs"; @Injectable() export class UserEffects { private actions$ = inject(Actions); private userService = inject(UserService); loadUser$ = createEffect(() => this.actions$.pipe( ofType(UserActions.loadUser), switchMap(({ userId }) => this.userService.getUser(userId).pipe( map((user) => UserActions.loadUserSuccess({ user })), catchError((error) => of(UserActions.loadUserFailure({ error: error.message })), ), ), ), ), ); } ``` ### Component Usage ```typescript @Component({ template: ` @if (loading()) { <app-spinner /> } @else if (user(); as user) { <h1>Welcome, {{ user.name }}</h1> <button (click)="logout()">Logout</button> } `, }) export class HeaderComponent { private store = inject(Store); user = this.store.selectSignal(selectUser); loading = this.store.selectSignal(selectUserLoading); logout() { this.store.dispatch(UserActions.logout()); } } ``` --- ## RxJS-Based Patterns ### Component Store (Local Feature State) ```typescript // stores/todo.store.ts import { Injectable } from "@angular/core"; import { ComponentStore } from "@ngrx/component-store"; import { switchMap, tap, catchError, EMPTY } from "rxjs"; interface TodoState { todos: Todo[]; loading: boolean; } @Injectable()
Ver en GitHub
Este SKILL.md es muy grande, por eso SkillsMP muestra aqui solo la primera seccion. Ver en GitHub