NgRx Signal Store vs NgRx Classique en 2026 : Comment Choisir ?
Comparaison approfondie entre NgRx Signal Store et NgRx classique pour la gestion d'état Angular. Découvrez quand utiliser chaque approche avec des exemples de code pratiques.

NgRx Signal Store représente un changement fondamental dans la gestion d'état Angular, remplaçant le pattern traditionnel Redux (actions-reducers-selectors) par une approche réactive basée sur les signaux. Avec NgRx 22 (publié en août 2026), Signal Store gagne les extensions resource, les signaux profonds dynamiques et une meilleure composabilité. Cette comparaison examine les deux solutions pour aider les développeurs Angular à prendre des décisions architecturales éclairées.
Utiliser Signal Store pour la gestion d'état au niveau fonctionnalité, les applications de complexité moyenne et l'intégration native avec l'API Resource d'Angular. Utiliser NgRx Classique pour les applications d'entreprise nécessitant des pistes d'audit strictes, des effets de bord complexes et un débogage extensif avec DevTools.
Comprendre les Différences Architecturales
NgRx classique suit le pattern Redux : les actions décrivent les événements, les reducers produisent un nouvel état immuable, et les selectors extraient les données. Chaque changement d'état passe par un pipeline prévisible et traçable.
Signal Store adopte une approche différente. Au lieu de dispatcher des actions via des reducers, l'état réside dans des signaux réactifs. Les mises à jour s'effectuent via des méthodes qui appellent patchState(), et les valeurs dérivées proviennent de signaux computed plutôt que de selectors mémorisés.
| Aspect | NgRx Classique | Signal Store |
|---|---|---|
| Modèle Mental | Redux/Flux | Services Réactifs |
| Boilerplate | Élevé (actions, reducers, selectors, effects) | Faible (state, methods, computed) |
| Réactivité | Observables RxJS | Signaux Angular |
| API Resource | Intégration manuelle | Native avec @ngrx/signals/resource |
| DevTools | Redux DevTools complet | Signal Store DevTools (via plugin) |
| Courbe d'apprentissage | Raide | Modérée |
| Taille du Bundle | Plus grande (~15-20KB gzippé) | Plus petite (~4KB gzippé) |
Pour les développeurs déjà familiers avec les Signaux Angular, Signal Store apparaît comme une extension naturelle des primitives réactives du framework.
NgRx Classique : Le Pattern Redux dans Angular
NgRx classique organise la gestion d'état autour des actions, reducers, selectors et effects. Cette séparation impose la prévisibilité au prix de la verbosité.
import { createActionGroup, emptyProps, props } from '@ngrx/store';
import { Book } from './book.model';
export const BooksActions = createActionGroup({
source: 'Books',
events: {
'Load Books': emptyProps(),
'Load Books Success': props<{ books: Book[] }>(),
'Load Books Failure': props<{ error: string }>(),
'Add Book': props<{ book: Book }>(),
'Remove Book': props<{ id: string }>(),
},
});L'API createActionGroup regroupe les actions liées sous une source unique, améliorant la lisibilité dans DevTools et réduisant le boilerplate par rapport aux appels individuels createAction.
import { createReducer, on } from '@ngrx/store';
import { BooksActions } from './books.actions';
import { Book } from './book.model';
export interface BooksState {
books: Book[];
loading: boolean;
error: string | null;
}
const initialState: BooksState = {
books: [],
loading: false,
error: null,
};
export const booksReducer = createReducer(
initialState,
on(BooksActions.loadBooks, (state) => ({
...state,
loading: true,
error: null,
})),
on(BooksActions.loadBooksSuccess, (state, { books }) => ({
...state,
books,
loading: false,
})),
on(BooksActions.loadBooksFailure, (state, { error }) => ({
...state,
loading: false,
error,
}))
);Les reducers restent des fonctions pures qui retournent de nouveaux objets d'état. La fonction on() gère la correspondance des actions et les mises à jour d'état de manière concise.
import { createFeatureSelector, createSelector } from '@ngrx/store';
import { BooksState } from './books.reducer';
export const selectBooksState = createFeatureSelector<BooksState>('books');
export const selectAllBooks = createSelector(
selectBooksState,
(state) => state.books
);
export const selectBooksLoading = createSelector(
selectBooksState,
(state) => state.loading
);
export const selectPublishedBooks = createSelector(
selectAllBooks,
(books) => books.filter((book) => book.published)
);Les selectors fournissent la mémoïsation automatiquement. Le selector selectPublishedBooks ne recalcule que lorsque le tableau de livres change, rendant les lectures efficaces même dans des stores fréquemment mis à jour.
NgRx Signal Store : La Gestion d'État Signaux-First
Signal Store consolide l'état, les valeurs computed et les méthodes dans un service injectable unique. La documentation officielle NgRx le décrit comme une "solution de gestion d'état complète avec support natif des Signaux Angular."
import { computed, inject } from '@angular/core';
import { patchState, signalStore, withComputed, withMethods, withState } from '@ngrx/signals';
import { BooksService } from './books.service';
import { Book } from './book.model';
interface BooksState {
books: Book[];
loading: boolean;
error: string | null;
}
const initialState: BooksState = {
books: [],
loading: false,
error: null,
};
export const BooksStore = signalStore(
{ providedIn: 'root' },
withState(initialState),
withComputed(({ books }) => ({
publishedBooks: computed(() => books().filter((book) => book.published)),
totalBooks: computed(() => books().length),
})),
withMethods((store, booksService = inject(BooksService)) => ({
async loadBooks() {
patchState(store, { loading: true, error: null });
try {
const books = await booksService.getAll();
patchState(store, { books, loading: false });
} catch (error) {
patchState(store, { loading: false, error: 'Failed to load books' });
}
},
addBook(book: Book) {
patchState(store, ({ books }) => ({ books: [...books, book] }));
},
removeBook(id: string) {
patchState(store, ({ books }) => ({
books: books.filter((book) => book.id !== id),
}));
},
}))
);L'ensemble de la gestion d'état d'une fonctionnalité tient dans un seul fichier. Les propriétés d'état deviennent automatiquement des signaux, les signaux computed remplacent les selectors, et les méthodes remplacent les dispatches d'actions.
NgRx 22 : Extensions Resource et withProps
NgRx 22 introduit le point d'entrée @ngrx/signals/resource, permettant une intégration directe avec l'API Resource d'Angular. La fonctionnalité withProps centralise l'injection de services, tandis que les extensions resource gèrent les patterns asynchrones courants.
import { computed, inject } from '@angular/core';
import { patchState, signalStore, withComputed, withMethods, withProps } from '@ngrx/signals';
import { extendResource, withPreviousValueOnLoading, withValueOnError } from '@ngrx/signals/resource';
import { httpResource } from '@angular/core';
import { BooksService } from './books.service';
import { Book } from './book.model';
export const BooksResourceStore = signalStore(
{ providedIn: 'root' },
// Injection de services via withProps (privé par convention avec underscore)
withProps(() => ({
_booksService: inject(BooksService),
})),
// Configuration des resources avec extensions
withProps((store) => ({
_booksResource: extendResource(
httpResource<Book[]>({
url: '/api/books',
defaultValue: [],
}),
withPreviousValueOnLoading(), // Conserve la dernière valeur pendant le rechargement
withValueOnError([]) // Retourne un tableau vide en cas d'erreur
),
})),
// Exposition en lecture seule aux consommateurs
withProps((store) => ({
booksResource: store._booksResource.asReadonly(),
})),
withComputed((store) => ({
books: computed(() => store.booksResource.value()),
isLoading: computed(() => store.booksResource.isLoading()),
publishedBooks: computed(() =>
store.booksResource.value().filter((book) => book.published)
),
}))
);Les extensions resource résolvent deux problèmes courants : le scintillement pendant les rechargements (corrigé par withPreviousValueOnLoading) et la gestion des états d'erreur (corrigée par withValueOnError ou withPreviousValueOnError). Ces extensions se composent et peuvent être fournies globalement via provideResourceExtensions.
Gestion des Entités avec withEntities
Les deux approches supportent les collections d'entités, mais la fonctionnalité withEntities de Signal Store réduit drastiquement le code.
import { computed, inject } from '@angular/core';
import { patchState, signalStore, withComputed, withMethods } from '@ngrx/signals';
import {
addEntity,
removeEntity,
setAllEntities,
updateEntity,
withEntities,
} from '@ngrx/signals/entities';
import { BooksService } from './books.service';
import { Book } from './book.model';
export const BooksEntityStore = signalStore(
{ providedIn: 'root' },
withEntities<Book>(),
withComputed(({ entities }) => ({
publishedBooks: computed(() => entities().filter((book) => book.published)),
})),
withMethods((store, booksService = inject(BooksService)) => ({
async loadBooks() {
const books = await booksService.getAll();
patchState(store, setAllEntities(books));
},
addBook(book: Book) {
patchState(store, addEntity(book));
},
updateBook(id: string, changes: Partial<Book>) {
patchState(store, updateEntity({ id, changes }));
},
removeBook(id: string) {
patchState(store, removeEntity(id));
},
}))
);La fonctionnalité withEntities fournit automatiquement les signaux ids, entityMap et entities. Les updaters d'entités comme addEntity, removeEntity et updateEntity gèrent les mutations de collections.
Prêt à réussir tes entretiens Angular ?
Entraîne-toi avec nos simulateurs interactifs, fiches express et tests techniques.
Features de Store Personnalisées pour la Réutilisabilité
La fonction signalStoreFeature de Signal Store permet d'extraire et de réutiliser des préoccupations transversales à travers les stores. NgRx 22 ajoute SignalStoreFeatureType pour simplifier l'extraction des types.
import { computed } from '@angular/core';
import { signalStoreFeature, withComputed, withState, SignalStoreFeatureType } from '@ngrx/signals';
type RequestStatus = 'idle' | 'pending' | 'fulfilled' | 'error';
export function withRequestStatus() {
return signalStoreFeature(
withState<{ requestStatus: RequestStatus }>({ requestStatus: 'idle' }),
withComputed(({ requestStatus }) => ({
isPending: computed(() => requestStatus() === 'pending'),
isFulfilled: computed(() => requestStatus() === 'fulfilled'),
isError: computed(() => requestStatus() === 'error'),
}))
);
}
// NgRx 22 : extraction des types sans redéclaration manuelle
export type RequestStatusFeature = SignalStoreFeatureType<typeof withRequestStatus>;
export function setPending(): { requestStatus: RequestStatus } {
return { requestStatus: 'pending' };
}
export function setFulfilled(): { requestStatus: RequestStatus } {
return { requestStatus: 'fulfilled' };
}
export function setError(): { requestStatus: RequestStatus } {
return { requestStatus: 'error' };
}Cette feature personnalisée peut maintenant être composée dans n'importe quel store nécessitant le suivi de l'état de chargement.
import { inject } from '@angular/core';
import { patchState, signalStore, withMethods } from '@ngrx/signals';
import { setAllEntities, withEntities } from '@ngrx/signals/entities';
import { withRequestStatus, setFulfilled, setPending, setError } from './with-request-status';
import { BooksService } from './books.service';
import { Book } from './book.model';
export const BooksStore = signalStore(
{ providedIn: 'root' },
withEntities<Book>(),
withRequestStatus(),
withMethods((store, booksService = inject(BooksService)) => ({
async loadBooks() {
patchState(store, setPending());
try {
const books = await booksService.getAll();
patchState(store, setAllEntities(books), setFulfilled());
} catch {
patchState(store, setError());
}
},
}))
);Le store composé expose maintenant les signaux computed isPending(), isFulfilled() et isError() sans dupliquer la logique.
Suivi Synchrone de l'État avec watchState
NgRx 22 ajoute la fonction watchState pour le suivi synchrone des changements d'état, utile pour les fonctionnalités undo/redo ou l'historique d'état.
import { inject, DestroyRef } from '@angular/core';
import { watchState } from '@ngrx/signals';
import { BooksStore } from './books.store';
export function setupStateHistory() {
const store = inject(BooksStore);
const destroyRef = inject(DestroyRef);
const history: unknown[] = [];
// Suivi synchrone : capture chaque changement d'état, sans coalescence
watchState(store, (state) => {
history.push(structuredClone(state));
});
return { history };
}Contrairement au suivi basé sur les effects qui coalesce les mises à jour, watchState se déclenche de manière synchrone à chaque appel patchState, capturant l'historique complet de l'état.
Patterns d'Intégration dans les Composants
Signal Store s'intègre naturellement avec la syntaxe des templates Angular via la lecture des signaux.
import { Component, inject } from '@angular/core';
import { BooksStore } from './books.store';
@Component({
selector: 'app-books-list',
standalone: true,
template: `
@if (store.isPending()) {
<div class="loading-spinner">Chargement des livres...</div>
}
@if (store.isError()) {
<div class="error-message">Échec du chargement des livres</div>
}
@for (book of store.entities(); track book.id) {
<div class="book-card">
<h3>{{ book.title }}</h3>
<p>{{ book.author }}</p>
<button (click)="store.removeBook(book.id)">Supprimer</button>
</div>
}
<p>Total : {{ store.totalBooks() }} livres</p>
`,
})
export class BooksListComponent {
readonly store = inject(BooksStore);
constructor() {
this.store.loadBooks();
}
}Pas de gestion des souscriptions. Pas de pipes async. Les signaux s'intègrent avec la détection de changement zoneless d'Angular pour des performances optimales. Avec OnPush comme stratégie de détection de changement par défaut dans Angular 22, les composants Signal Store bénéficient de mises à jour précises et granulaires.
Considérations de Performance et Taille de Bundle
L'architecture plus légère de Signal Store se traduit par des bundles plus petits. Le package core @ngrx/signals ajoute environ 4KB gzippé, comparé à 15-20KB pour la stack NgRx classique complète (@ngrx/store, @ngrx/effects, @ngrx/entity).
Signal Store bénéficie également de la détection de changement basée sur les signaux d'Angular. Les signaux computed ne recalculent que lorsque les dépendances changent, et les composants ne se re-rendent que lorsque les signaux consommés sont mis à jour. Cette réactivité granulaire élimine les cycles de détection de changement inutiles courants dans les patterns basés sur les Observables.
Pour les applications utilisant déjà RxJS intensivement, les selectors basés sur les Observables de NgRx classique s'intègrent parfaitement avec les pipelines existants.
NgRx 22 nécessite Angular 22.x, TypeScript 6.0.x et RxJS 6.5.x ou 7.5.x. Mettre à jour avec ng update @ngrx/signals@22 pour les projets Signal Store.
Stratégie de Migration de Classique vers Signal Store
Migrer une application NgRx classique existante ne nécessite pas une réécriture complète. Les deux solutions peuvent coexister, permettant une adoption incrémentale.
- Commencer par les nouvelles fonctionnalités avec Signal Store
- Migrer les modules de fonctionnalités isolés un par un
- Conserver les fonctionnalités complexes avec beaucoup d'effects sur NgRx classique si le coût de migration dépasse le bénéfice
- Utiliser les utilitaires d'interopérabilité NgRx Signals pour créer des ponts entre l'état basé sur les Observables et celui basé sur les signaux
NgRx Store classique et Signal Store peuvent fonctionner côte à côte dans la même application. L'équipe NgRx supporte officiellement les deux approches et fournit des utilitaires d'interopérabilité pour créer des ponts entre elles.
Quand Choisir Chaque Approche
Choisir Signal Store quand :
- Construction de nouvelles applications Angular 22+
- Gestion de l'état au niveau fonctionnalité ou composant
- Intégration avec l'API Resource d'Angular et les Signal Forms
- Priorité à l'expérience développeur et à la réduction du boilerplate
- L'équipe a une expérience NgRx limitée
- La taille du bundle est importante
Choisir NgRx Classique quand :
- Maintenance d'applications NgRx existantes
- Besoin de pistes d'audit strictes avec logging des actions
- Construction de workflows complexes avec des effets de bord extensifs
- Besoin du débogage time-travel complet de Redux DevTools
- L'équipe possède une expertise NgRx approfondie
Pour les fondamentaux de la gestion d'état, comprendre les deux approches prépare les développeurs à divers codebases Angular.
Sources
- NgRx v22 Release Announcement - Extensions resource, signaux profonds dynamiques, SignalStoreFeatureType
- Angular 22 Feature Overview - Signal Forms stable, OnPush par défaut, API Resource
- Using Angular's Resource API with NgRx Signal Store - Patterns withProps, intégration httpResource
Passe à la pratique !
Teste tes connaissances avec nos simulateurs d'entretien et tests techniques.
Signal Store vs NgRx Classique : Points Clés à Retenir
- Signal Store réduit le boilerplate de gestion d'état de 60 à 70% par rapport à NgRx classique
- NgRx 22 ajoute les extensions resource (
withPreviousValueOnLoading,withValueOnError) pour une gestion transparente de l'état asynchrone - La fonctionnalité
withPropscentralise l'injection de services, éliminant les listes de paramètres répétées entre les features watchStatepermet le suivi synchrone de l'état pour les fonctionnalités undo/redo et l'historique d'état- NgRx classique reste le meilleur choix pour les applications d'entreprise nécessitant des pistes d'audit strictes et des effets de bord complexes
- Les deux approches coexistent dans la même application, permettant une migration incrémentale
- Pour les nouveaux projets Angular 22+, Signal Store devrait être le choix par défaut sauf si des exigences spécifiques demandent NgRx classique
Tu saurais repérer le bug en Angular ?
Un vrai bout de code, un bug caché, une tentative par jour. Sans compte pour essayer.

Écrit par
Anthony Fillion-MailletFondateur de SharpSkill
Développeur fullstack depuis plus de 10 ans. Il dirige SharpSkill et répond de tout ce qui y est publié.
Mis à jour le 9 octobre 2026
Partager
Articles similaires

Missions freelance développeur Angular en 2026 : où trouver des contrats, TJM, comment se préparer
Le marché freelance Angular en 2026 : plateformes spécialisées, TJM médian de 440 EUR, compétences recherchées et préparation aux entretiens techniques.

Emplois développeur Angular en 2026 : où chercher, grilles de salaires, préparation aux entretiens
Les offres d'emploi pour développeurs Angular restent nombreuses en Europe à la fin 2026. Découvrez où trouver les postes, les salaires du marché et comment préparer les entretiens.

Angular Signals et Computed en 2026 : Réactivité Fine et Questions d'Entretien
Maîtriser les Signals Angular, computed, effect et linkedSignal dans Angular 20+. Patterns de réactivité fine et préparation aux entretiens techniques.