NgRx Signal Store vs Classic NgRx di 2026: Mana yang Harus Dipilih?
Perbandingan lengkap NgRx Signal Store dan Classic NgRx untuk manajemen state Angular. Pelajari kapan menggunakan masing-masing pendekatan dengan contoh kode praktis.

NgRx Signal Store merepresentasikan pergeseran fundamental dalam manajemen state Angular, menggantikan pola tradisional actions-reducers-selectors yang terinspirasi Redux dengan pendekatan reaktif berbasis signal. Dengan NgRx 22 (dirilis Agustus 2026), Signal Store mendapatkan resource extensions, dynamic deep signals, dan composability yang lebih baik. Perbandingan ini mengkaji kedua solusi untuk membantu pengembang Angular membuat keputusan arsitektural yang tepat.
Gunakan Signal Store untuk state tingkat fitur, aplikasi kompleksitas menengah, dan integrasi dengan Resource API native Angular. Gunakan Classic NgRx untuk aplikasi enterprise yang membutuhkan audit trail ketat, efek samping kompleks, dan debugging DevTools yang ekstensif.
Memahami Perbedaan Arsitektural
Classic NgRx mengikuti pola Redux: actions mendeskripsikan event, reducers menghasilkan state baru yang immutable, dan selectors mengekstrak data. Setiap perubahan state mengalir melalui pipeline yang dapat diprediksi dan dilacak.
Signal Store mengambil pendekatan berbeda. Alih-alih mengirimkan actions melalui reducers, state hidup dalam signals reaktif. Update terjadi melalui metode yang memanggil patchState(), dan nilai turunan berasal dari computed signals bukan selectors yang di-memoize.
| Aspek | Classic NgRx | Signal Store |
|---|---|---|
| Model Mental | Redux/Flux | Layanan Reaktif |
| Boilerplate | Tinggi (actions, reducers, selectors, effects) | Rendah (state, methods, computed) |
| Reaktivitas | RxJS Observables | Angular Signals |
| Resource API | Integrasi manual | Native dengan @ngrx/signals/resource |
| DevTools | Redux DevTools Lengkap | Signal Store DevTools (via plugin) |
| Kurva Pembelajaran | Curam | Moderat |
| Ukuran Bundle | Lebih Besar (~15-20KB gzipped) | Lebih Kecil (~4KB gzipped) |
Bagi pengembang yang sudah familiar dengan Angular Signals, Signal Store terasa seperti ekstensi natural dari primitif reaktif framework.
Classic NgRx: Pola Redux dalam Angular
Classic NgRx mengorganisir manajemen state di sekitar actions, reducers, selectors, dan effects. Pemisahan ini memaksakan prediktabilitas dengan biaya verbositas.
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 mengelompokkan actions terkait di bawah satu source, meningkatkan keterbacaan DevTools dan mengurangi boilerplate dibandingkan dengan panggilan createAction individual.
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 tetap merupakan fungsi murni yang mengembalikan objek state baru. Fungsi on() menangani pencocokan action dan update state secara ringkas.
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 menyediakan memoization secara otomatis. Selector selectPublishedBooks hanya menghitung ulang ketika array books berubah, membuat pembacaan efisien bahkan di store yang sering diupdate.
NgRx Signal Store: Manajemen State Signals-First
Signal Store mengkonsolidasikan state, nilai computed, dan methods ke dalam satu layanan injectable. Dokumentasi resmi NgRx menggambarkannya sebagai "solusi manajemen state berfitur lengkap dengan dukungan native untuk 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),
}));
},
}))
);Seluruh manajemen state fitur muat dalam satu file. Properti state menjadi signals secara otomatis, computed signals menggantikan selectors, dan methods menggantikan dispatch actions.
NgRx 22: Resource Extensions dan withProps
NgRx 22 memperkenalkan entry point @ngrx/signals/resource, memungkinkan integrasi langsung dengan Resource API Angular. Fitur withProps memusatkan injeksi service, sementara resource extensions menangani pola async umum.
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' },
// Inject services via withProps (private by convention with underscore)
withProps(() => ({
_booksService: inject(BooksService),
})),
// Set up resources with extensions
withProps((store) => ({
_booksResource: extendResource(
httpResource<Book[]>({
url: '/api/books',
defaultValue: [],
}),
withPreviousValueOnLoading(), // Keeps last value while reloading
withValueOnError([]) // Returns empty array on error
),
})),
// Expose read-only resource to consumers
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 menyelesaikan dua masalah umum: flickering saat reload (diperbaiki oleh withPreviousValueOnLoading) dan penanganan error state (diperbaiki oleh withValueOnError atau withPreviousValueOnError). Extensions ini dapat dikomposisi dan dapat disediakan secara global via provideResourceExtensions.
Manajemen Entity dengan withEntities
Kedua pendekatan mendukung koleksi entity, tetapi fitur withEntities Signal Store secara dramatis mengurangi kode.
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));
},
}))
);Fitur withEntities menyediakan signals ids, entityMap, dan entities secara otomatis. Updaters entity seperti addEntity, removeEntity, dan updateEntity menangani mutasi koleksi.
Siap menguasai wawancara Angular Anda?
Berlatih dengan simulator interaktif, flashcards, dan tes teknis kami.
Fitur Store Kustom untuk Reusability
Fungsi signalStoreFeature Signal Store memungkinkan cross-cutting concerns diekstrak dan digunakan kembali di berbagai store. NgRx 22 menambahkan SignalStoreFeatureType untuk menyederhanakan ekstraksi tipe.
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: extract types without manual redeclaration
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' };
}Fitur kustom ini sekarang dapat dikomposisikan ke store mana pun yang membutuhkan pelacakan status loading.
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());
}
},
}))
);Store yang dikomposisikan sekarang mengekspos computed signals isPending(), isFulfilled(), dan isError() tanpa menduplikasi logika.
Pelacakan State Sinkron dengan watchState
NgRx 22 menambahkan fungsi watchState untuk pelacakan perubahan state secara sinkron, berguna untuk fungsionalitas undo/redo atau riwayat state.
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[] = [];
// Synchronous tracking: captures every state change, no coalescing
watchState(store, (state) => {
history.push(structuredClone(state));
});
return { history };
}Berbeda dengan pelacakan berbasis effect yang menggabungkan update, watchState dijalankan secara sinkron pada setiap panggilan patchState, menangkap riwayat state lengkap.
Pola Integrasi Komponen
Signal Store terintegrasi secara natural dengan sintaks template Angular melalui pembacaan signal.
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">Memuat buku...</div>
}
@if (store.isError()) {
<div class="error-message">Gagal memuat buku</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)">Hapus</button>
</div>
}
<p>Total: {{ store.totalBooks() }} buku</p>
`,
})
export class BooksListComponent {
readonly store = inject(BooksStore);
constructor() {
this.store.loadBooks();
}
}Tidak ada manajemen subscription. Tidak ada async pipes. Signals terintegrasi dengan deteksi perubahan zoneless Angular untuk performa optimal. Dengan strategi deteksi perubahan OnPush sebagai default di Angular 22, komponen Signal Store mendapat manfaat dari update yang presisi dan granular.
Pertimbangan Performa dan Ukuran Bundle
Arsitektur Signal Store yang lebih ringan menghasilkan bundle yang lebih kecil. Paket inti @ngrx/signals menambahkan sekitar 4KB gzipped, dibandingkan dengan 15-20KB untuk stack classic NgRx lengkap (@ngrx/store, @ngrx/effects, @ngrx/entity).
Signal Store juga mendapat manfaat dari deteksi perubahan berbasis signal Angular. Computed signals hanya menghitung ulang ketika dependensi berubah, dan komponen hanya me-render ulang ketika signals yang dikonsumsi diupdate. Reaktivitas granular ini mengeliminasi siklus deteksi perubahan yang tidak perlu yang umum dalam pola berbasis Observable.
Untuk aplikasi yang sudah menggunakan RxJS secara intensif, selectors berbasis Observable classic NgRx terintegrasi dengan mulus dengan pipeline yang ada.
NgRx 22 membutuhkan Angular 22.x, TypeScript 6.0.x, dan RxJS 6.5.x atau 7.5.x. Update dengan ng update @ngrx/signals@22 untuk proyek Signal Store.
Strategi Migrasi dari Classic ke Signal Store
Memigrasi aplikasi classic NgRx yang sudah ada tidak memerlukan rewrite besar-besaran. Kedua solusi dapat berdampingan, memungkinkan adopsi inkremental.
- Mulai dengan fitur baru menggunakan Signal Store
- Migrasikan modul fitur yang terisolasi satu per satu
- Pertahankan fitur kompleks dengan banyak effects di classic NgRx jika biaya migrasi melebihi manfaatnya
- Gunakan utilitas interop NgRx Signals untuk menjembatani antara state berbasis Observable dan berbasis signal
Classic NgRx Store dan Signal Store dapat berjalan berdampingan dalam aplikasi yang sama. Tim NgRx secara resmi mendukung kedua pendekatan dan menyediakan utilitas interop untuk menjembatani keduanya.
Kapan Memilih Setiap Pendekatan
Pilih Signal Store ketika:
- Membangun aplikasi Angular 22+ baru
- Mengelola state tingkat fitur atau komponen
- Mengintegrasikan dengan Resource API dan Signal Forms Angular
- Memprioritaskan pengalaman pengembang dan pengurangan boilerplate
- Tim memiliki pengalaman NgRx terbatas
- Ukuran bundle penting
Pilih Classic NgRx ketika:
- Memelihara aplikasi NgRx yang sudah ada
- Membutuhkan logging action dan audit trail yang ketat
- Membangun workflow kompleks dengan efek samping ekstensif
- Memerlukan debugging time-travel Redux DevTools lengkap
- Tim memiliki keahlian NgRx yang mendalam
Untuk dasar-dasar manajemen state, memahami kedua pendekatan mempersiapkan pengembang untuk berbagai codebase Angular.
Sources
- NgRx v22 Release Announcement - Resource extensions, dynamic deep signals, SignalStoreFeatureType
- Angular 22 Feature Overview - Signal Forms stable, OnPush default, Resource API
- Using Angular's Resource API with NgRx Signal Store - withProps patterns, httpResource integration
Mulai berlatih!
Uji pengetahuan Anda dengan simulator wawancara dan tes teknis kami.
Signal Store vs Classic NgRx: Poin Penting
- Signal Store mengurangi boilerplate manajemen state sebesar 60-70% dibandingkan classic NgRx
- NgRx 22 menambahkan resource extensions (
withPreviousValueOnLoading,withValueOnError) untuk penanganan state async yang mulus - Fitur
withPropsmemusatkan injeksi service, mengeliminasi daftar parameter berulang di berbagai fitur watchStatememungkinkan pelacakan state sinkron untuk undo/redo dan riwayat state- Classic NgRx tetap menjadi pilihan lebih baik untuk aplikasi enterprise yang membutuhkan audit trail ketat dan efek samping kompleks
- Kedua pendekatan dapat berdampingan dalam aplikasi yang sama, memungkinkan migrasi inkremental
- Untuk proyek Angular 22+ baru, Signal Store harus menjadi pilihan default kecuali persyaratan spesifik menuntut classic NgRx
Bisakah kamu menemukan bug di Angular?
Satu potongan kode nyata, satu bug tersembunyi, satu percobaan per hari. Tanpa akun untuk mencoba.

Ditulis oleh
Anthony Fillion-MailletPendiri SharpSkill
Developer fullstack selama lebih dari 10 tahun. Ia menjalankan SharpSkill dan bertanggung jawab atas semua yang diterbitkan di sini.
Diperbarui 9 Oktober 2026
Tag
Bagikan
Artikel terkait

RxJS di Angular 2026: Operator, Subject, dan Interop Signals
RxJS di Angular 2026: kuasai operator, Subject, dan pola interop Signals yang dipakai developer Angular di produksi, plus pertanyaan wawancara yang paling sering muncul.

Angular Resource API dan httpResource: Panduan Lengkap (Stabil di Angular 22)
Resource API dan httpResource Angular kini stabil sejak Angular 22. Panduan ini mencakup resource(), rxResource(), httpResource(), helper chain() untuk resource bergantung, validasi Zod, dan pertanyaan wawancara.

Dependency Injection Lanjutan di Angular 2026: Providers, Tokens, dan Pertanyaan Interview
Pelajari sistem dependency injection Angular secara mendalam termasuk strategi provider, InjectionToken, injector hierarkis, dan pertanyaan interview umum untuk developer Angular berpengalaman.