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.

Perbandingan manajemen state NgRx Signal Store vs Classic NgRx Angular

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.

Panduan Keputusan Cepat

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.

AspekClassic NgRxSignal Store
Model MentalRedux/FluxLayanan Reaktif
BoilerplateTinggi (actions, reducers, selectors, effects)Rendah (state, methods, computed)
ReaktivitasRxJS ObservablesAngular Signals
Resource APIIntegrasi manualNative dengan @ngrx/signals/resource
DevToolsRedux DevTools LengkapSignal Store DevTools (via plugin)
Kurva PembelajaranCuramModerat
Ukuran BundleLebih 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.

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 }>(),
  },
});

API createActionGroup mengelompokkan actions terkait di bawah satu source, meningkatkan keterbacaan DevTools dan mengurangi boilerplate dibandingkan dengan panggilan createAction individual.

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,
  }))
);

Reducers tetap merupakan fungsi murni yang mengembalikan objek state baru. Fungsi on() menangani pencocokan action dan update state secara ringkas.

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)
);

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."

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),
      }));
    },
  }))
);

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.

books-resource.store.tstypescript
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.

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));
    },
  }))
);

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.

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

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());
      }
    },
  }))
);

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.

state-history.tstypescript
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.

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">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.

Persyaratan NgRx 22

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

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 withProps memusatkan injeksi service, mengeliminasi daftar parameter berulang di berbagai fitur
  • watchState memungkinkan 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
Tantangan harian

Bisakah kamu menemukan bug di Angular?

Satu potongan kode nyata, satu bug tersembunyi, satu percobaan per hari. Tanpa akun untuk mencoba.

Anthony Fillion-Maillet

Ditulis oleh

Anthony Fillion-Maillet

Pendiri 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

#angular
#ngrx
#state-management
#signals
#typescript

Bagikan

Artikel terkait