S

super-dev — full-stack

@super-dev.app · Full-stack · ★ ouvert aux missions
12,8k abonnés·47 vidéos·Actif depuis 2017

Développeur full-stack .NET / Angular avec une casquette DevOps sur Azure Cloud. Aussi à l'aise sur Flutter + Firebase. Je construis des produits, je les déploie, je les surveille.

Retour aux articles
ANGULAR
ANGULAR

État applicatif avec NgRx SignalStore

Article 4 sur 4 — Angular 21 en pratique
Zoneless, signals, resource(), @defer, SignalStore — le front moderne, en vrai.

Tout l'état d'une app ne tient pas dans un signal() perdu au fond d'un composant. Dès qu'un état est partagé, dérivé et muté depuis plusieurs endroits, on veut une frontière claire : des sélecteurs en lecture seule, des méthodes pour le faire évoluer. NgRx SignalStore ( @ngrx/signals ) offre exactement ça, sans le boilerplate des actions/reducers du NgRx classique.

Anatomie d'un signalStore

Un store se compose de features chaînées. withState déclare l'état initial, withComputed les valeurs dérivées, withMethods les opérations. Chaque champ d'état devient automatiquement un signal exposé sur l'instance.

TypeScript
1import { signalStore, withComputed, withMethods, withState } from '@ngrx/signals';
2import { computed } from '@angular/core';
3
4export const CartStore = signalStore(
5 { providedIn: 'root' },
6 withState<{ items: CartItem[] }>({ items: [] }),
7 withComputed(({ items }) => ({
8 total: computed(() => items().reduce((sum, item) => sum + item.price * item.quantity, 0)),
9 count: computed(() => items().length),
10 })),
11 withMethods((store) => ({
12 add(item: CartItem): void {
13 patchState(store, { items: [...store.items(), item] });
14 },
15 clear(): void {
16 patchState(store, { items: [] });
17 },
18 })),
19);

L'état n'est jamais muté directement : on passe par patchState , qui applique une mise à jour immuable et notifie les signals concernés.

Des sélecteurs qui sont des signals

store.total et store.count ne sont pas des fonctions à appeler dans le service : ce sont des computed , donc des signals à part entière. Dans un composant, on les lit comme n'importe quel signal, et le change detection zoneless ne re-rend que ce qui en dépend.

TypeScript
1export class CartBadge {
2 private readonly store = inject(CartStore);
3
4 protected readonly count = this.store.count;
5 protected readonly total = this.store.total;
6
7 protected checkout(): void {
8 this.store.clear();
9 }
10}

Composer avec des appels async

withMethods peut intégrer rxMethod (depuis @ngrx/signals/rxjs-interop ) pour brancher un flux RxJS, ou simplement async / await pour un fetch . On garde la logique d'orchestration dans le store, le composant reste une vue. C'est aussi là qu'on pose un état loading pour un pattern stale-while-revalidate.

Store ou simple signal ?

Tout n'a pas besoin d'un store. Un état local à un composant — un onglet actif, l'ouverture d'un menu — reste un signal() privé : un store y ajouterait de l'indirection inutile. Le SignalStore se justifie quand l'état est :

  • partagé entre plusieurs composants ou routes ;
  • dérivé par plusieurs computed qu'on veut centraliser ;
  • muté par des opérations qu'on veut tester en isolation.

La règle pratique : commence avec des signals locaux, extrais un store le jour où tu copies le même état dans un deuxième composant. La doc couvre chaque feature dans le guide SignalStore .

Le SignalStore n'est pas le NgRx « actions partout » d'hier. C'est une façade de signals : lecture seule en sortie, méthodes en entrée, zéro reducer. Tu gardes la discipline d'un store sans payer son cérémonial.
super-dev — portfolio.app
// Continuer dans ANGULAR
Un moteur façon DOOM dans le navigateur, à trois backends
11 min • 1,4k lectures
Passer une app Angular en zoneless + signals
8 min • 4,1k lectures
Charger des données avec resource() et httpResource()
7 min • 3,6k lectures