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 vs NgRx Classique en 2026 : Comment Choisir ?

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.

Guide de Décision Rapide

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.

AspectNgRx ClassiqueSignal Store
Modèle MentalRedux/FluxServices Réactifs
BoilerplateÉlevé (actions, reducers, selectors, effects)Faible (state, methods, computed)
RéactivitéObservables RxJSSignaux Angular
API ResourceIntégration manuelleNative avec @ngrx/signals/resource
DevToolsRedux DevTools completSignal Store DevTools (via plugin)
Courbe d'apprentissageRaideModérée
Taille du BundlePlus 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é.

books.actions.tstypescript
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.

books.reducer.tstypescript
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.

books.selectors.tstypescript
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."

books.store.tstypescript
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.

books-resource.store.tstypescript
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.

books-entity.store.tstypescript
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.

with-request-status.tstypescript
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.

books-with-status.store.tstypescript
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.

state-history.tstypescript
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.

books-list.component.tstypescript
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.

Prérequis NgRx 22

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

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é withProps centralise l'injection de services, éliminant les listes de paramètres répétées entre les features
  • watchState permet 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
Défi du jour

Tu saurais repérer le bug en Angular ?

Un vrai bout de code, un bug caché, une tentative par jour. Sans compte pour essayer.

Anthony Fillion-Maillet

Écrit par

Anthony Fillion-Maillet

Fondateur 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