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.

Zur Installation springen

Quellinformationen

Repository
Zidong-LLC/BIBLIOTECA
Letzte Quellaktivität
11. März 2026 um 13:02
Erkannte Sprache von SKILL.md
Englisch
Sterne
36
Forks
22

Installationsoptionen

Standardmäßig ist der Prompt ausgewählt, der zuerst die Quelle prüft. Sie können zu einem direkten Befehl wechseln oder eine lokale Kopie herunterladen.

Quelldateien prüfen

Lesen Sie SKILL.md und alle von SkillsMP angezeigten Begleitdateien, bevor Sie sich für eine Installation entscheiden.

Datei-Explorer
3 Dateien

SKILL.md wird angezeigt

SKILL.md
Quellanweisungen · Schreibgeschützte Vorschau
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()
Auf GitHub ansehen
Diese SKILL.md ist sehr gross, daher zeigt SkillsMP hier nur den ersten Abschnitt. Auf GitHub ansehen