NgRx Signal Store vs Classic NgRx di 2026: Mana yang Harus Dipilih?
Perbandingan mendalam NgRx Signal Store dan Classic NgRx untuk manajemen state Angular di 2026. Pelajari kelebihan, kekurangan, dan kapan menggunakan masing-masing pendekatan.

NgRx Signal Store merepresentasikan pergeseran fundamental dalam manajemen state Angular, menggantikan pola tradisional actions-reducers-selectors yang terinspirasi Redux dengan pendekatan reaktif berbasis signal. Perbandingan ini mengkaji kedua solusi untuk membantu pengembang Angular membuat keputusan arsitektural yang tepat di tahun 2026.
Gunakan Signal Store untuk state tingkat fitur dan aplikasi kompleksitas menengah di mana pengurangan boilerplate penting. 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 | | DevTools | Redux DevTools Lengkap | Signal Store DevTools (via plugin) | | Kurva Pembelajaran | Curam | Moderat | | Ukuran Bundle | Lebih Besar | Lebih Kecil |
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.
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.
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' };
}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.
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.
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.
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 20+ baru
- Mengelola state tingkat fitur atau komponen
- 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.
Mulai berlatih!
Uji pengetahuan Anda dengan simulator wawancara dan tes teknis kami.
Kesimpulan
- Signal Store mengurangi boilerplate manajemen state sebesar 60-70% dibandingkan classic NgRx
- 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
- Computed signals Signal Store menyediakan memoization otomatis tanpa definisi selector eksplisit
- Fitur store kustom (
signalStoreFeature) memungkinkan cross-cutting concerns yang dapat digunakan kembali - Untuk proyek Angular 20+ baru di 2026, Signal Store harus menjadi pilihan default kecuali persyaratan spesifik menuntut classic NgRx
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 @defer di 2026: Lazy Loading Deklaratif untuk Aplikasi Lebih Cepat
Blok @defer pada Angular memungkinkan lazy loading deklaratif dengan trigger viewport, interaction, dan timer. Panduan mendalam tentang prefetching, SSR, incremental hydration, dan pola optimasi performa.

Angular 20 di 2026: Resource API, httpResource, dan Pertanyaan Wawancara
Angular 20 memperkenalkan httpResource dan menstabilkan Resource API untuk pengambilan data berbasis signal. Tutorial praktis yang mencakup resource(), rxResource(), httpResource(), validasi Zod, dan pertanyaan wawancara umum.