NgRx Signal Store vs klassisches NgRx 2026: Welche Lösung passt zu Ihrem Projekt?

Vergleich von NgRx Signal Store und klassischem NgRx für Angular State Management. NgRx 22 Features, Code-Beispiele und Empfehlungen für die richtige Wahl.

NgRx Signal Store vs klassisches NgRx 2026: Welche Lösung passt zu Ihrem Projekt?

NgRx Signal Store stellt einen fundamentalen Wandel im Angular State Management dar. Er ersetzt das traditionelle Redux-inspirierte Pattern aus Actions, Reducern und Selektoren durch einen signalbasierten reaktiven Ansatz. Mit NgRx 22 (veröffentlicht August 2026) erhält Signal Store Resource Extensions, dynamische Deep Signals und verbesserte Kompositionsmöglichkeiten. Dieser Vergleich untersucht beide Lösungen, um Angular-Entwicklern fundierte architektonische Entscheidungen zu ermöglichen.

Schnelle Entscheidungshilfe

Der Signal Store eignet sich für Feature-Level State, mittelkomplexe Anwendungen und die Integration mit Angulars nativer Resource API. Klassisches NgRx empfiehlt sich für Enterprise-Anwendungen mit strengen Audit-Anforderungen, komplexen Side Effects und umfangreichem DevTools-Debugging.

Architektonische Unterschiede verstehen

Klassisches NgRx folgt dem Redux-Pattern: Actions beschreiben Ereignisse, Reducer erzeugen neuen immutablen State, und Selektoren extrahieren Daten. Jede State-Änderung durchläuft eine vorhersagbare, nachvollziehbare Pipeline.

Der Signal Store verfolgt einen anderen Ansatz. Anstatt Actions durch Reducer zu dispatchen, lebt der State in reaktiven Signals. Updates erfolgen durch Methoden, die patchState() aufrufen, und abgeleitete Werte stammen aus Computed Signals statt aus memoiserten Selektoren.

AspektKlassisches NgRxSignal Store
Mentales ModellRedux/FluxReaktive Services
BoilerplateHoch (Actions, Reducer, Selektoren, Effects)Niedrig (State, Methoden, Computed)
ReaktivitätRxJS ObservablesAngular Signals
Resource APIManuelle IntegrationNativ mit @ngrx/signals/resource
DevToolsVollständige Redux DevToolsSignal Store DevTools (via Plugin)
LernkurveSteilModerat
Bundle-GrößeGrößer (~15-20KB gzipped)Kleiner (~4KB gzipped)

Für Entwickler, die bereits mit Angular Signals vertraut sind, fühlt sich der Signal Store wie eine natürliche Erweiterung der reaktiven Framework-Primitive an.

Klassisches NgRx: Das Redux Pattern in Angular

Klassisches NgRx organisiert das State Management rund um Actions, Reducer, Selektoren und Effects. Diese Trennung erzwingt Vorhersagbarkeit auf Kosten von Ausführlichkeit.

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 }>(),
  },
});

Die createActionGroup-API gruppiert zusammengehörige Actions unter einer einzigen Quelle und verbessert die DevTools-Lesbarkeit. Im Vergleich zu individuellen createAction-Aufrufen reduziert sich der Boilerplate-Code erheblich.

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,
  }))
);

Reducer bleiben pure Funktionen, die neue State-Objekte zurückgeben. Die on()-Funktion übernimmt das Action-Matching und die State-Updates prägnant.

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)
);

Selektoren bieten automatische Memoisation. Der selectPublishedBooks-Selektor berechnet nur dann neu, wenn sich das Books-Array ändert, was Lesezugriffe auch bei häufig aktualisierten Stores effizient macht.

NgRx Signal Store: Signals-First State Management

Der Signal Store konsolidiert State, Computed Values und Methoden in einem einzigen injizierbaren Service. Die offizielle NgRx-Dokumentation beschreibt ihn als eine "vollständig ausgestattete State-Management-Lösung mit nativer Unterstützung für Angular Signals".

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),
      }));
    },
  }))
);

Das gesamte State Management eines Features passt in eine einzige Datei. State-Properties werden automatisch zu Signals, Computed Signals ersetzen Selektoren, und Methoden ersetzen das Action-Dispatching.

NgRx 22: Resource Extensions und withProps

NgRx 22 führt den Entry Point @ngrx/signals/resource ein, der eine direkte Integration mit Angulars Resource API ermöglicht. Das withProps-Feature zentralisiert die Service-Injection, während Resource Extensions gängige asynchrone Patterns behandeln.

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' },
  // Services über withProps injizieren (privat per Konvention mit Unterstrich)
  withProps(() => ({
    _booksService: inject(BooksService),
  })),
  // Resources mit Extensions einrichten
  withProps((store) => ({
    _booksResource: extendResource(
      httpResource<Book[]>({
        url: '/api/books',
        defaultValue: [],
      }),
      withPreviousValueOnLoading(),  // Behält letzten Wert während des Neuladens
      withValueOnError([])           // Gibt leeres Array bei Fehler zurück
    ),
  })),
  // Readonly Resource für Consumer bereitstellen
  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)
    ),
  }))
);

Resource Extensions lösen zwei häufige Probleme: Flackern während des Neuladens (behoben durch withPreviousValueOnLoading) und Fehlerbehandlung (behoben durch withValueOnError oder withPreviousValueOnError). Diese Extensions lassen sich komponieren und können global über provideResourceExtensions bereitgestellt werden.

Entity Management mit withEntities

Beide Ansätze unterstützen Entity-Collections, aber das withEntities-Feature des Signal Store reduziert den Code dramatisch.

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));
    },
  }))
);

Das withEntities-Feature stellt automatisch ids, entityMap und entities Signals bereit. Entity-Updater wie addEntity, removeEntity und updateEntity übernehmen die Collection-Mutationen.

Bereit für deine Angular-Interviews?

Übe mit unseren interaktiven Simulatoren, Flashcards und technischen Tests.

Custom Store Features für Wiederverwendbarkeit

Die signalStoreFeature-Funktion des Signal Store ermöglicht es, querschnittliche Belange zu extrahieren und über Stores hinweg wiederzuverwenden. NgRx 22 fügt SignalStoreFeatureType hinzu, um die Typextraktion zu vereinfachen.

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: Typen ohne manuelle Neudeklaration extrahieren
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' };
}

Dieses Custom Feature kann nun in jeden Store komponiert werden, der Loading-State-Tracking benötigt.

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());
      }
    },
  }))
);

Der komponierte Store stellt nun isPending(), isFulfilled() und isError() Computed Signals bereit, ohne Logik zu duplizieren.

Synchrones State-Tracking mit watchState

NgRx 22 fügt die watchState-Funktion für synchrones State-Change-Tracking hinzu, nützlich für Undo/Redo-Funktionalität oder State-History.

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[] = [];

  // Synchrones Tracking: erfasst jede State-Änderung, kein Coalescing
  watchState(store, (state) => {
    history.push(structuredClone(state));
  });

  return { history };
}

Im Gegensatz zu Effect-basiertem Tracking, das Updates zusammenfasst, feuert watchState synchron bei jedem patchState-Aufruf und erfasst die vollständige State-History.

Component Integration Patterns

Der Signal Store integriert sich natürlich mit Angulars Template-Syntax durch Signal-Reads.

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">Loading books...</div>
    }

    @if (store.isError()) {
      <div class="error-message">Failed to load books</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)">Remove</button>
      </div>
    }

    <p>Total: {{ store.totalBooks() }} books</p>
  `,
})
export class BooksListComponent {
  readonly store = inject(BooksStore);

  constructor() {
    this.store.loadBooks();
  }
}

Kein Subscription-Management erforderlich. Keine Async Pipes. Signals integrieren sich mit Angulars zoneless Change Detection für optimale Performance. Mit Angular 22s OnPush als Standard-Change-Detection-Strategie profitieren Signal Store Components von präzisen, granularen Updates.

Performance und Bundle-Größe

Die leichtere Architektur des Signal Store führt zu kleineren Bundles. Das Core-Paket @ngrx/signals fügt etwa 4KB gzipped hinzu, verglichen mit 15-20KB für den vollständigen klassischen NgRx-Stack (@ngrx/store, @ngrx/effects, @ngrx/entity).

Der Signal Store profitiert außerdem von Angulars signalbasierter Change Detection. Computed Signals berechnen nur neu, wenn sich Abhängigkeiten ändern, und Components rendern nur neu, wenn sich konsumierte Signals aktualisieren. Diese granulare Reaktivität eliminiert unnötige Change-Detection-Zyklen, die in Observable-basierten Patterns üblich sind.

Für Anwendungen, die RxJS bereits intensiv nutzen, integrieren sich die Observable-basierten Selektoren von klassischem NgRx nahtlos mit bestehenden Pipelines.

NgRx 22 Anforderungen

NgRx 22 erfordert Angular 22.x, TypeScript 6.0.x und RxJS 6.5.x oder 7.5.x. Update mit ng update @ngrx/signals@22 für Signal Store Projekte.

Migrationsstrategie von klassischem zu Signal Store

Die Migration einer bestehenden klassischen NgRx-Anwendung erfordert keinen Big-Bang-Rewrite. Beide Lösungen können koexistieren und ermöglichen eine inkrementelle Adoption.

  • Starten mit neuen Features unter Verwendung des Signal Store
  • Migration isolierter Feature-Module einzeln
  • Komplexe, stark effect-lastige Features auf klassischem NgRx belassen, wenn die Migrationskosten den Nutzen übersteigen
  • NgRx Signals Interop-Utilities zur Brückenbildung zwischen Observable-basiertem und signalbasiertem State nutzen

Klassischer NgRx Store und Signal Store können parallel in derselben Anwendung laufen. Das NgRx-Team unterstützt offiziell beide Ansätze und stellt Interop-Utilities zur Brückenbildung bereit.

Wann welchen Ansatz wählen

Signal Store empfiehlt sich wenn:

  • Neue Angular 22+ Anwendungen entwickelt werden
  • Feature-Level oder Component-Level State verwaltet wird
  • Integration mit Angulars Resource API und Signal Forms erfolgt
  • Developer Experience und reduzierter Boilerplate Priorität haben
  • Das Team begrenzte NgRx-Erfahrung hat
  • Bundle-Größe wichtig ist

Klassisches NgRx empfiehlt sich wenn:

  • Bestehende NgRx-Anwendungen gewartet werden
  • Strenge Action-Protokollierung und Audit-Trails erforderlich sind
  • Komplexe Workflows mit umfangreichen Side Effects gebaut werden
  • Vollständiges Redux DevTools Time-Travel-Debugging benötigt wird
  • Das Team tiefgehende NgRx-Expertise besitzt

Für State Management Grundlagen bereitet das Verständnis beider Ansätze Entwickler auf diverse Angular-Codebasen vor.

Quellen

Fang an zu üben!

Teste dein Wissen mit unseren Interview-Simulatoren und technischen Tests.

Signal Store vs klassisches NgRx: Das Wichtigste

  • Signal Store reduziert State-Management-Boilerplate um 60-70% im Vergleich zu klassischem NgRx
  • NgRx 22 fügt Resource Extensions (withPreviousValueOnLoading, withValueOnError) für nahtloses asynchrones State-Handling hinzu
  • Das withProps-Feature zentralisiert die Service-Injection und eliminiert wiederholte Parameterlisten über Features hinweg
  • watchState ermöglicht synchrones State-Tracking für Undo/Redo und State-History
  • Klassisches NgRx bleibt die bessere Wahl für Enterprise-Anwendungen mit strengen Audit-Anforderungen und komplexen Side Effects
  • Beide Ansätze können in derselben Anwendung koexistieren und ermöglichen inkrementelle Migration
  • Für neue Angular 22+ Projekte sollte Signal Store die Standardwahl sein, es sei denn, spezifische Anforderungen erfordern klassisches NgRx
Tägliche Challenge

Findest du den Bug in Angular?

Ein echter Codeausschnitt, ein versteckter Bug, ein Versuch pro Tag. Zum Ausprobieren ohne Konto.

Anthony Fillion-Maillet

Geschrieben von

Anthony Fillion-Maillet

Gründer von SharpSkill

Seit über 10 Jahren Fullstack-Entwickler. Er leitet SharpSkill und verantwortet alles, was hier erscheint.

Aktualisiert am 9. Oktober 2026

Teilen

Verwandte Artikel