NgRx Signal Store vs klassieke NgRx in 2026: welke oplossing past bij jouw Angular project?
Uitgebreide vergelijking tussen NgRx Signal Store en klassieke NgRx Store in 2026. Architectuur, performance, boilerplate-reductie en migratiestrategieën voor Angular state management.

NgRx Signal Store vertegenwoordigt een fundamentele verschuiving in Angular state management. Het vervangt het traditionele Redux-geïnspireerde patroon van actions, reducers en selectors door een signal-gebaseerde reactieve aanpak. Deze vergelijking onderzoekt beide oplossingen om Angular-ontwikkelaars te helpen weloverwogen architecturale beslissingen te nemen in 2026.
Gebruik Signal Store voor feature-level state en middelmatig complexe applicaties waar boilerplate-reductie belangrijk is. Gebruik klassieke NgRx voor enterprise applicaties die strikte audit trails, complexe side effects en uitgebreide DevTools debugging vereisen.
De architecturale verschillen begrijpen
Klassieke NgRx volgt het Redux patroon: actions beschrijven gebeurtenissen, reducers produceren nieuwe immutable state, en selectors extraheren data. Elke state-wijziging doorloopt een voorspelbare, traceerbare pipeline.
Signal Store hanteert een andere aanpak. In plaats van actions door reducers te dispatchen, leeft de state in reactieve signals. Updates gebeuren via methodes die patchState() aanroepen, en afgeleide waarden komen van computed signals in plaats van gememoriseerde selectors.
| Aspect | Klassieke NgRx | Signal Store | |--------|----------------|---------------| | Mentaal model | Redux/Flux | Reactieve services | | Boilerplate | Hoog (actions, reducers, selectors, effects) | Laag (state, methodes, computed) | | Reactiviteit | RxJS Observables | Angular Signals | | DevTools | Volledige Redux DevTools | Signal Store DevTools (via plugin) | | Leercurve | Steil | Gematigd | | Bundle grootte | Groter | Kleiner |
Voor ontwikkelaars die al bekend zijn met Angular Signals, voelt Signal Store als een natuurlijke uitbreiding van de reactieve framework primitieven.
Klassieke NgRx: het Redux patroon in Angular
Klassieke NgRx organiseert state management rond actions, reducers, selectors en effects. Deze scheiding dwingt voorspelbaarheid af ten koste van uitgebreidheid.
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 }>(),
},
});De createActionGroup API groepeert gerelateerde actions onder één bron, wat de leesbaarheid in DevTools verbetert en boilerplate vermindert in vergelijking met individuele createAction aanroepen.
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,
}))
);Reducers blijven pure functies die nieuwe state objecten retourneren. De on() functie handelt action matching en state updates beknopt af.
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)
);Selectors bieden automatisch memoization. De selectPublishedBooks selector herberekent alleen wanneer de books array verandert, waardoor reads efficiënt zijn zelfs in vaak bijgewerkte stores.
NgRx Signal Store: signals-first state management
Signal Store consolideert state, computed waarden en methodes in één injecteerbare service. De officiële NgRx documentatie beschrijft het als een "volledig uitgeruste state management oplossing met native ondersteuning voor Angular Signals".
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),
}));
},
}))
);Het volledige state management van een feature past in één bestand. State properties worden automatisch signals, computed signals vervangen selectors, en methodes vervangen action dispatches.
Entity management met withEntities
Beide benaderingen ondersteunen entity collecties, maar de withEntities feature van Signal Store reduceert de code drastisch.
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));
},
}))
);De withEntities feature biedt automatisch ids, entityMap en entities signals. Entity updaters zoals addEntity, removeEntity en updateEntity handelen collectie mutaties af.
Klaar om je Angular gesprekken te halen?
Oefen met onze interactieve simulatoren, flashcards en technische tests.
Custom Store Features voor herbruikbaarheid
De signalStoreFeature functie van Signal Store maakt het mogelijk om cross-cutting concerns te extraheren en te hergebruiken over 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' };
}Deze custom feature kan nu gecomposeerd worden in elke store die loading state tracking nodig heeft.
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());
}
},
}))
);De gecomposeerde store stelt nu isPending(), isFulfilled() en isError() computed signals beschikbaar zonder logica te dupliceren.
Component integratie patronen
Signal Store integreert natuurlijk met Angulars template syntax door signal reads.
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();
}
}Geen subscription management nodig. Geen async pipes. Signals integreren met Angulars zoneless change detection voor optimale performance.
Performance en bundle grootte overwegingen
De lichtere architectuur van Signal Store resulteert in kleinere bundles. Het core @ngrx/signals package voegt ongeveer 4KB gzipped toe, vergeleken met 15-20KB voor de volledige klassieke NgRx stack (@ngrx/store, @ngrx/effects, @ngrx/entity).
Signal Store profiteert ook van Angulars signal-gebaseerde change detection. Computed signals herberekenen alleen wanneer dependencies veranderen, en componenten re-renderen alleen wanneer geconsumeerde signals updaten. Deze granulaire reactiviteit elimineert onnodige change detection cycli die gebruikelijk zijn in Observable-gebaseerde patronen.
Voor applicaties die RxJS al intensief gebruiken, integreren de Observable-gebaseerde selectors van klassieke NgRx naadloos met bestaande pipelines.
Migratiestrategie van klassiek naar Signal Store
Het migreren van een bestaande klassieke NgRx applicatie vereist geen big-bang herschrijving. Beide oplossingen kunnen naast elkaar bestaan, wat incrementele adoptie mogelijk maakt.
- Begin met nieuwe features met Signal Store
- Migreer geïsoleerde feature modules één voor één
- Behoud complexe, effect-intensieve features op klassieke NgRx als de migratiekosten de voordelen overtreffen
- Gebruik de NgRx Signals interop utilities om te bridgen tussen Observable-gebaseerde en signal-gebaseerde state
Klassieke NgRx Store en Signal Store kunnen naast elkaar draaien in dezelfde applicatie. Het NgRx team ondersteunt officieel beide benaderingen en biedt interop utilities om ze te verbinden.
Wanneer elke aanpak kiezen
Kies Signal Store wanneer:
- Nieuwe Angular 20+ applicaties gebouwd worden
- Feature-level of component-level state beheerd wordt
- Developer experience en gereduceerde boilerplate prioriteit hebben
- Het team beperkte NgRx ervaring heeft
- Bundle grootte belangrijk is
Kies klassieke NgRx wanneer:
- Bestaande NgRx applicaties onderhouden worden
- Strikte action logging en audit trails vereist zijn
- Complexe workflows met uitgebreide side effects gebouwd worden
- Volledige Redux DevTools time-travel debugging nodig is
- Het team diepgaande NgRx expertise bezit
Voor state management fundamenten bereidt het begrijpen van beide benaderingen ontwikkelaars voor op diverse Angular codebases.
Begin met oefenen!
Test je kennis met onze gespreksimulatoren en technische tests.
Conclusie
- Signal Store reduceert state management boilerplate met 60-70% vergeleken met klassieke NgRx
- Klassieke NgRx blijft de betere keuze voor enterprise applicaties die strikte audit trails en complexe side effects vereisen
- Beide benaderingen kunnen naast elkaar bestaan in dezelfde applicatie, wat incrementele migratie mogelijk maakt
- Computed signals van Signal Store bieden automatische memoization zonder expliciete selector definities
- Custom store features (
signalStoreFeature) maken herbruikbare cross-cutting concerns mogelijk - Voor nieuwe Angular 20+ projecten in 2026 zou Signal Store de standaardkeuze moeten zijn tenzij specifieke requirements klassieke NgRx vereisen
Delen
Gerelateerde artikelen

RxJS in Angular 2026: operators, Subjects en Signals-interop
RxJS in Angular 2026: de operators, Subjects en Signals-interop-patronen die Angular-ontwikkelaars in productie gebruiken, plus de meest voorkomende sollicitatievragen.

Angular @defer in 2026: Declaratief Lazy Loading voor Snellere Applicaties
Beheers Angular @defer-blokken voor declaratief lazy loading. Diepgaande analyse van triggers, prefetching, incrementele hydratie, SSR-gedrag en performancepatronen uit de praktijk.

Angular 20 in 2026: Resource API, httpResource en sollicitatievragen
Angular 20 introduceert de Resource API en httpResource als signal-gebaseerde alternatieven voor handmatige HttpClient-subscriptions. Deze handleiding behandelt alle drie Resource-varianten, Zod-validatie en veelgestelde sollicitatievragen.