Day 16 — State Management with NgRx SignalStore
📘 Day 16 — State Management with NgRx SignalStore
Zero to Hero — Hands-on Angular Tutorial
Today you will learn:
- ✔️ Why use a State Library vs. Manual Services?
- ✔️ NgRx SignalStore: The modern, lightweight alternative to Redux
- ✔️
signalStore,withState,withMethods,withComputed - ✔️ Modifying state with
patchState - ✔️ Using the store in Components
In Day 7, you built a manual store. Today, we effectively “Go Pro” using the official NgRx SignalStore library—the new standard for Angular state management. 🚀
🟦 1. Why NgRx SignalStore?
You can manage state manually (like we did in Day 7), but SignalStore gives you:
- Standardization: A structure every developer understands.
- Immutability:
patchStatehandles updates safely. - Extensibility: Add plugins for LocalStorage, Entity Management, etc., easily.
- DevTools: Easier debugging.
It is simpler than the old Redux (Actions/Reducers) pattern but just as powerful for 90% of apps.
🟩 2. Installation
npm install @ngrx/signals
🟧 3. Creating Your First SignalStore
Let’s convert our “Counter” or “User” logic into a formal Store.
user.store.ts
import { signalStore, withState, withMethods, withComputed, patchState } from '@ngrx/signals';
import { computed } from '@angular/core';
type UserState = {
name: string;
isAdmin: boolean;
score: number;
};
const initialState: UserState = {
name: 'Guest',
isAdmin: false,
score: 0
};
export const UserStore = signalStore(
{ providedIn: 'root' }, // Makes it injectable everywhere
withState(initialState),
// 1. Computed Values (Derived State)
withComputed(({ score, isAdmin }) => ({
badge: computed(() => (score() > 50 ? 'Gold' : 'Silver')),
title: computed(() => (isAdmin() ? 'Admin User' : 'Standard User'))
})),
// 2. Methods (Actions/Updaters)
withMethods((store) => ({
updateName(name: string) {
patchState(store, { name });
},
incrementScore() {
// Functional update (using previous state)
patchState(store, (state) => ({ score: state.score + 10 }));
},
promoteToAdmin() {
patchState(store, { isAdmin: true });
},
reset() {
patchState(store, initialState);
}
}))
);
🟥 4. Using the Store in a Component
It works exactly like a Service, but simpler!
user-dashboard.component.ts
import { Component, inject } from '@angular/core';
import { UserStore } from './user.store';
@Component({
selector: 'app-user-dashboard',
standalone: true,
template: `
<h1>Hello, {{ store.name() }}</h1>
<p>Rank: {{ store.title() }} ({{ store.badge() }})</p>
<p>Score: {{ store.score() }}</p>
<button (click)="store.incrementScore()">Score +10</button>
<button (click)="store.promoteToAdmin()">Make Admin</button>
<button (click)="store.reset()">Reset</button>
`,
// 👇 INJECT THE STORE HERE
providers: [UserStore] // Or omit if providedIn: 'root'
})
export class UserDashboardComponent {
// Inject exactly like a service
readonly store = inject(UserStore);
}
Notice: No subscribe, no async pipe, no selectors. Just properties.
🟫 5. Async Calls (RxJS Integration)
Stores often need to load data from an API (Side Effects).
We use rxMethod for this.
Updates to UserStore:
import { inject } from '@angular/core';
import { rxMethod } from '@ngrx/signals/rxjs-interop';
import { pipe, switchMap, tap } from 'rxjs';
import { HttpClient } from '@angular/common/http';
export const UserStore = signalStore(
// ... state setup ...
withMethods((store, http = inject(HttpClient)) => ({
// Define an RxJS Method pipeline
loadUserById: rxMethod<string>(
pipe(
switchMap((id) => http.get<any>(`/api/users/${id}`)),
tap((user) => patchState(store, {
name: user.name,
score: user.score
}))
)
)
}))
);
Usage:
ngOnInit() {
this.store.loadUserById('123'); // Triggers the API call
}
🟦 6. Lifecycle Hooks in Store
You can run code when the store initializes using withHooks.
import { withHooks } from '@ngrx/signals';
export const UserStore = signalStore(
// ...
withHooks({
onInit(store) {
console.log('Store initialized!');
// Good place to load initial data from LocalStorage
},
onDestroy(store) {
console.log('Store destroyed');
}
})
);
🎉 End of Day 16 — What You Learned
Today you adopted the industry-standard NgRx SignalStore:
- ✔️ Structure: State, Computed, Methods, Hooks.
- ✔️ patchState: The safe way to update data.
- ✔️ rxMethod: Handling Async/HTTP calls inside the store.
- ✔️ Simplicity: No reducers, no actions, just code.
This architecture scales from small apps to massive enterprise systems.
🧪 Day 16 Challenge
Build a “Shopping Cart Store” using NgRx SignalStore.
Requirements:
- State:
items(array),loading(boolean). - Computed:
totalPrice,itemCount. - Methods:
addItem(item),removeItem(id),checkout(). - Async:
checkout()should simulate a 2-second API delay, setloadingto true, then clear the cart. - Hook: Log “Cart Ready” when the store starts.