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

Vergleich zwischen NgRx Signal Store und klassischem NgRx Store im Jahr 2026. Architektur, Performance, Boilerplate-Code und Migrationsstrategien für Angular State Management.

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

Der NgRx Signal Store stellt einen grundlegenden Paradigmenwechsel im Angular State Management dar. Er ersetzt das traditionelle Redux-inspirierte Pattern aus Actions, Reducern und Selektoren durch einen signalbasierten reaktiven Ansatz. Dieser Vergleich untersucht beide Lösungen, um Angular-Entwicklern fundierte architektonische Entscheidungen im Jahr 2026 zu ermöglichen.

Schnelle Entscheidungshilfe

Der Signal Store eignet sich für Feature-Level State und mittelkomplexe Anwendungen, bei denen Boilerplate-Reduzierung wichtig ist. 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.

| Aspekt | Klassisches NgRx | Signal Store | |--------|------------------|---------------| | Mentales Modell | Redux/Flux | Reaktive Services | | Boilerplate | Hoch (Actions, Reducer, Selektoren, Effects) | Niedrig (State, Methoden, Computed) | | Reaktivität | RxJS Observables | Angular Signals | | DevTools | Vollständige Redux DevTools | Signal Store DevTools (via Plugin) | | Lernkurve | Steil | Moderat | | Bundle-Größe | Größer | Kleiner |

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.

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.

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

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.

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.

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.

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
Koexistenz funktioniert

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 20+ Anwendungen entwickelt werden
  • Feature-Level oder Component-Level State verwaltet wird
  • 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.

Fang an zu üben!

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

Fazit

  • Signal Store reduziert State-Management-Boilerplate um 60-70% im Vergleich zu klassischem NgRx
  • 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
  • Computed Signals des Signal Store bieten automatische Memoisation ohne explizite Selektor-Definitionen
  • Custom Store Features (signalStoreFeature) ermöglichen wiederverwendbare querschnittliche Belange
  • Für neue Angular 20+ Projekte in 2026 sollte Signal Store die Standardwahl sein, es sei denn, spezifische Anforderungen erfordern klassisches NgRx

Teilen

Verwandte Artikel