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 en 2026. Découvrez les différences architecturales, exemples de code et conseils pour choisir la bonne solution.

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. Cette comparaison examine les deux solutions pour aider les développeurs Angular à prendre des décisions architecturales éclairées en 2026.
Utiliser Signal Store pour la gestion d'état au niveau fonctionnalité et les applications de complexité moyenne où la réduction du boilerplate est importante. 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 | | DevTools | Redux DevTools complet | Signal Store DevTools (via plugin) | | Courbe d'apprentissage | Raide | Modérée | | Taille du Bundle | Plus grande | Plus petite |
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.
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.
import { computed } from '@angular/core';
import { signalStoreFeature, withComputed, withState } 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'),
}))
);
}
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.
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.
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.
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 20+
- Gestion de l'état au niveau fonctionnalité ou composant
- 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.
Passe à la pratique !
Teste tes connaissances avec nos simulateurs d'entretien et tests techniques.
Conclusion
- Signal Store réduit le boilerplate de gestion d'état de 60 à 70% par rapport à NgRx classique
- 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 peuvent coexister dans la même application, permettant une migration incrémentale
- Les signaux computed de Signal Store fournissent une mémoïsation automatique sans définitions explicites de selectors
- Les features de store personnalisées (
signalStoreFeature) permettent des préoccupations transversales réutilisables - Pour les nouveaux projets Angular 20+ en 2026, Signal Store devrait être le choix par défaut sauf si des exigences spécifiques demandent NgRx classique
Partager
Articles similaires

RxJS dans Angular 2026 : operators, Subjects et interop Signals
RxJS dans Angular 2026 : maîtriser les operators, les Subjects et les patterns d'interop Signals utilisés en production, ainsi que les questions d'entretien les plus fréquentes.

Angular @defer en 2026 : lazy loading declaratif complet
Plongee technique dans les blocs Angular @defer pour le lazy loading declaratif. Declencheurs, prefetching, hydratation incrementale, comportement SSR et patterns de performance concrets pour preparer les entretiens techniques Angular.

Angular 20 en 2026 : Resource API, httpResource et questions d'entretien
Angular 20 introduit httpResource et stabilise la Resource API pour la récupération de données fondée sur les signals. Un tutoriel pratique sur resource(), rxResource(), httpResource(), la validation Zod et les questions d'entretien courantes.