# NgRx Signal Store vs Klasyczny NgRx w 2026: Który Wybrać? > Kompleksowe porównanie NgRx Signal Store i klasycznego NgRx do zarządzania stanem w Angular. Praktyczne przykłady kodu i rekomendacje na rok 2026. - Published: 2026-07-11 - Updated: 2026-07-11 - Author: SharpSkill - Tags: angular, ngrx, state-management, signals, typescript - Reading time: 9 min --- NgRx Signal Store reprezentuje fundamentalną zmianę w zarządzaniu stanem Angular, zastępując tradycyjny wzorzec Redux (akcje-reducery-selektory) reaktywnym podejściem opartym na sygnałach. To porównanie analizuje oba rozwiązania, aby pomóc programistom Angular podjąć świadome decyzje architektoniczne w 2026 roku. > **Szybki przewodnik decyzyjny** > > Należy używać **Signal Store** dla stanu na poziomie feature'ów i aplikacji o średniej złożoności, gdzie redukcja boilerplate'u ma znaczenie. **Klasyczny NgRx** sprawdzi się w aplikacjach enterprise wymagających ścisłych ścieżek audytu, złożonych efektów ubocznych i rozbudowanego debugowania w DevTools. ## Zrozumienie Różnic Architektonicznych Klasyczny NgRx opiera się na [wzorcu Redux](https://redux.js.org/understanding/thinking-in-redux/three-principles): akcje opisują zdarzenia, reducery produkują nowy niemutowalny stan, a selektory wydobywają dane. Każda zmiana stanu przepływa przez przewidywalny, możliwy do śledzenia pipeline. Signal Store przyjmuje inne podejście. Zamiast dispatch'owania akcji przez reducery, stan żyje w reaktywnych sygnałach. Aktualizacje odbywają się przez metody wywołujące `patchState()`, a wartości pochodne pochodzą z computed signals zamiast memoizowanych selektorów. | Aspekt | Klasyczny NgRx | Signal Store | |--------|---------------|---------------| | Model mentalny | Redux/Flux | Reaktywne serwisy | | Boilerplate | Wysoki (akcje, reducery, selektory, efekty) | Niski (stan, metody, computed) | | Reaktywność | RxJS Observables | Angular Signals | | DevTools | Pełne Redux DevTools | Signal Store DevTools (plugin) | | Krzywa uczenia | Stroma | Umiarkowana | | Rozmiar bundle | Większy | Mniejszy | Dla programistów znających już Angular Signals, Signal Store wydaje się naturalnym rozszerzeniem reaktywnych prymitywów frameworka. ## Klasyczny NgRx: Wzorzec Redux w Angular Klasyczny NgRx organizuje zarządzanie stanem wokół akcji, reducerów, selektorów i efektów. Ta separacja wymusza przewidywalność kosztem wielosłowności. ```typescript // books.actions.ts 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 }>(), }, }); ``` API `createActionGroup` grupuje powiązane akcje pod jednym źródłem, poprawiając czytelność w DevTools i redukując boilerplate w porównaniu do pojedynczych wywołań `createAction`. ```typescript // books.reducer.ts 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, })) ); ``` Reducery pozostają czystymi funkcjami zwracającymi nowe obiekty stanu. Funkcja `on()` obsługuje dopasowywanie akcji i aktualizacje stanu w zwięzły sposób. ```typescript // books.selectors.ts import { createFeatureSelector, createSelector } from '@ngrx/store'; import { BooksState } from './books.reducer'; export const selectBooksState = createFeatureSelector('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) ); ``` Selektory automatycznie zapewniają memoizację. Selektor `selectPublishedBooks` przelicza się tylko gdy tablica książek się zmienia, czyniąc odczyty wydajnymi nawet w często aktualizowanych store'ach. ## NgRx Signal Store: Zarządzanie Stanem Oparte na Sygnałach Signal Store konsoliduje stan, wartości computed i metody w pojedynczym injektowalnym serwisie. [Oficjalna dokumentacja NgRx](https://ngrx.io/guide/signals/signal-store) opisuje go jako "w pełni funkcjonalne rozwiązanie do zarządzania stanem z natywnym wsparciem dla Angular Signals." ```typescript // books.store.ts 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), })); }, })) ); ``` Całe zarządzanie stanem feature'a mieści się w jednym pliku. Właściwości stanu automatycznie stają się sygnałami, computed signals zastępują selektory, a metody zastępują dispatch'owanie akcji. ## Zarządzanie Encjami z withEntities Oba podejścia wspierają kolekcje encji, ale funkcja `withEntities` w Signal Store drastycznie redukuje ilość kodu. ```typescript // books-entity.store.ts 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(), 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) { patchState(store, updateEntity({ id, changes })); }, removeBook(id: string) { patchState(store, removeEntity(id)); }, })) ); ``` Funkcja `withEntities` automatycznie dostarcza sygnały `ids`, `entityMap` i `entities`. Updatery encji jak `addEntity`, `removeEntity` i `updateEntity` obsługują mutacje kolekcji. ## Własne Feature'y Store dla Reużywalności Funkcja `signalStoreFeature` w Signal Store umożliwia ekstrakcję i ponowne użycie cross-cutting concerns w różnych store'ach. ```typescript // with-request-status.ts 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' }; } ``` Ten własny feature może być teraz skomponowany w dowolnym store'ze wymagającym śledzenia stanu ładowania. ```typescript // books-with-status.store.ts 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(), 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()); } }, })) ); ``` Skomponowany store teraz eksponuje computed signals `isPending()`, `isFulfilled()` i `isError()` bez duplikowania logiki. ## Wzorce Integracji z Komponentami Signal Store naturalnie integruje się ze składnią szablonów Angular poprzez odczyty sygnałów. ```typescript // books-list.component.ts import { Component, inject } from '@angular/core'; import { BooksStore } from './books.store'; @Component({ selector: 'app-books-list', standalone: true, template: ` @if (store.isPending()) {
Ładowanie książek...
} @if (store.isError()) {
Nie udało się załadować książek
} @for (book of store.entities(); track book.id) {

{{ book.title }}

{{ book.author }}

}

Razem: {{ store.totalBooks() }} książek

`, }) export class BooksListComponent { readonly store = inject(BooksStore); constructor() { this.store.loadBooks(); } } ``` Brak zarządzania subskrypcjami. Brak async pipe'ów. Sygnały integrują się z zoneless change detection w Angular dla optymalnej wydajności. ## Wydajność i Rozmiar Bundle Lżejsza architektura Signal Store przekłada się na mniejsze bundle. Podstawowy pakiet `@ngrx/signals` dodaje około 4KB po gzip, w porównaniu do 15-20KB dla pełnego stosu klasycznego NgRx (`@ngrx/store`, `@ngrx/effects`, `@ngrx/entity`). Signal Store korzysta również z opartego na sygnałach change detection w Angular. Computed signals przeliczają się tylko gdy zależności się zmieniają, a komponenty re-renderują się tylko gdy konsumowane sygnały się aktualizują. Ta granularna reaktywność eliminuje niepotrzebne cykle change detection typowe dla wzorców opartych na Observable. Dla aplikacji już intensywnie używających RxJS, oparte na Observable selektory klasycznego NgRx bezproblemowo integrują się z istniejącymi pipeline'ami. ## Strategia Migracji z Klasycznego do Signal Store Migracja istniejącej aplikacji klasycznego NgRx nie wymaga przepisania wszystkiego naraz. Oba rozwiązania mogą współistnieć, umożliwiając stopniową adopcję. - Rozpoczęcie nowych feature'ów z użyciem Signal Store - Migracja izolowanych modułów feature'ów pojedynczo - Pozostawienie złożonych feature'ów z wieloma efektami na klasycznym NgRx, jeśli koszt migracji przewyższa korzyści - Użycie narzędzi interoperacyjności [NgRx Signals](https://ngrx.io/guide/signals) do łączenia stanu opartego na Observable ze stanem opartym na sygnałach > **Współistnienie działa** > > Klasyczny NgRx Store i Signal Store mogą działać obok siebie w tej samej aplikacji. Zespół NgRx oficjalnie wspiera oba podejścia i dostarcza narzędzia interoperacyjności do łączenia ich. ## Kiedy Wybrać Które Podejście ### Wybierz Signal Store gdy: - Budowanie nowych aplikacji Angular 20+ - Zarządzanie stanem na poziomie feature'ów lub komponentów - Priorytetem jest developer experience i redukcja boilerplate'u - Zespół ma ograniczone doświadczenie z NgRx - Rozmiar bundle ma znaczenie ### Wybierz klasyczny NgRx gdy: - Utrzymywanie istniejących aplikacji NgRx - Wymagane są ścisłe ścieżki logowania akcji i audytu - Budowanie złożonych workflow'ów z rozbudowanymi efektami ubocznymi - Potrzeba pełnego time-travel debugging w Redux DevTools - Zespół ma głęboką ekspertyzę NgRx Zrozumienie podstaw zarządzania stanem w obu podejściach przygotowuje programistów do pracy z różnorodnymi bazami kodu Angular. ## Podsumowanie - Signal Store redukuje boilerplate zarządzania stanem o 60-70% w porównaniu do klasycznego NgRx - Klasyczny NgRx pozostaje lepszym wyborem dla aplikacji enterprise wymagających ścisłych ścieżek audytu i złożonych efektów ubocznych - Oba podejścia mogą współistnieć w tej samej aplikacji, umożliwiając stopniową migrację - Computed signals w Signal Store zapewniają automatyczną memoizację bez jawnych definicji selektorów - Własne feature'y store (`signalStoreFeature`) umożliwiają reużywalne cross-cutting concerns - Dla nowych projektów Angular 20+ w 2026 roku Signal Store powinien być domyślnym wyborem, chyba że specyficzne wymagania wymagają klasycznego NgRx --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/pl/blog/angular/ngrx-signal-store-vs-classic-ngrx-comparison