NgRx State Management: Panduan Komprehensif Lintas Modul Angular
Manajemen state adalah aspek penting dalam membangun aplikasi Angular yang kompleks. NgRx, sebuah pustaka manajemen state reaktif untuk Angular, menyediakan solusi terpusat untuk mengelola state aplikasi Anda secara terprediksi dan efisien. Artikel ini akan membahas secara mendalam bagaimana menerapkan NgRx untuk manajemen state lintas modul dalam aplikasi Angular Anda, memastikan skalabilitas, pemeliharaan, dan organisasi kode yang baik.
Daftar Isi
- Pendahuluan untuk NgRx dan Manajemen State
- Mengapa Manajemen State Lintas Modul Penting?
- Menyiapkan Lingkungan NgRx di Aplikasi Angular Anda
- Membuat State, Aksi, Reducer, dan Efek Tingkat Tinggi
- Menerapkan Manajemen State di Dalam Satu Modul
- Berbagi State Antar Modul Menggunakan Selektor
- Memuat State Modul Secara Lambat dengan Efek
- Menggunakan Fitur Reducer untuk Modularitas yang Lebih Baik
- Praktik Terbaik untuk Manajemen State NgRx Lintas Modul
- Tips Pemecahan Masalah Umum NgRx
- Kesimpulan: Kekuatan NgRx dalam Aplikasi Angular Modular
1. Pendahuluan untuk NgRx dan Manajemen State
Sebelum kita mempelajari manajemen state lintas modul, mari kita pahami dulu apa itu NgRx dan mengapa manajemen state penting.
- Apa itu Manajemen State?
Manajemen state melibatkan pengelolaan data aplikasi Anda dari waktu ke waktu. Dalam aplikasi Angular, data ini dapat berupa apa saja mulai dari data pengguna hingga status UI. - Mengapa Manajemen State Penting?
Tanpa manajemen state yang tepat, aplikasi Anda dapat menjadi sulit untuk diprediksi, didebug, dan dipelihara, terutama saat aplikasi bertambah besar dan lebih kompleks. - Pengantar NgRx
NgRx adalah kerangka kerja manajemen state reaktif untuk aplikasi Angular, terinspirasi oleh Redux. Ini menyediakan toko terpusat untuk menyimpan seluruh state aplikasi, memastikan bahwa semua komponen memiliki akses ke data yang sama dan perubahan state dapat diprediksi. - Komponen Utama NgRx
- State: Sumber kebenaran tunggal untuk aplikasi Anda.
- Aksi: Objek yang mendeskripsikan peristiwa yang terjadi dalam aplikasi.
- Reducer: Fungsi murni yang menentukan bagaimana state berubah sebagai respons terhadap aksi.
- Efek: Menangani efek samping, seperti panggilan API, dan memicu aksi berdasarkan hasil.
- Selektor: Fungsi murni yang mengambil bagian dari state toko.
2. Mengapa Manajemen State Lintas Modul Penting?
Aplikasi Angular sering diatur ke dalam modul untuk modularitas dan organisasi yang lebih baik. Manajemen state lintas modul menjadi penting ketika beberapa modul perlu berbagi dan memodifikasi state yang sama.
- Modularitas: Aplikasi dipecah menjadi modul yang lebih kecil dan independen untuk meningkatkan pemeliharaan.
- Berbagi Data: Komponen dalam modul yang berbeda sering kali perlu mengakses dan memodifikasi state yang sama.
- Konsistensi: Manajemen state terpusat memastikan bahwa semua modul beroperasi dengan tampilan data yang konsisten.
- Penghindaran State Lokal: Mengelola state secara lokal di setiap modul dapat menyebabkan inkonsistensi dan kesalahan duplikasi.
- Skalabilitas: Aplikasi dengan manajemen state lintas modul yang terstruktur dengan baik lebih mudah diskalakan dan dipelihara.
3. Menyiapkan Lingkungan NgRx di Aplikasi Angular Anda
Sebelum kita dapat mulai menggunakan NgRx untuk manajemen state lintas modul, kita perlu menyiapkan lingkungan NgRx kita. Berikut adalah langkah-langkah untuk menginstal dan mengkonfigurasi NgRx di aplikasi Angular Anda:
- Instal Paket NgRx
Gunakan npm atau yarn untuk menginstal paket NgRx yang diperlukan:
npm install @ngrx/store @ngrx/effects @ngrx/store-devtools --save
- Mengimpor
StoreModule
keAppModule
Impor
StoreModule
keAppModule
Anda dan konfigurasikan dengan reducer root Anda:import { NgModule } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; import { StoreModule } from '@ngrx/store'; import { reducers, metaReducers } from './reducers'; // Define your reducers import { AppComponent } from './app.component'; @NgModule({ declarations: [AppComponent], imports: [ BrowserModule, StoreModule.forRoot(reducers, { metaReducers }), ], providers: [], bootstrap: [AppComponent], }) export class AppModule {}
- Mengkonfigurasi
StoreDevtoolsModule
(Opsional)Untuk pengalaman debug yang lebih baik, konfigurasikan
StoreDevtoolsModule
:import { StoreDevtoolsModule } from '@ngrx/store-devtools'; import { environment } from '../environments/environment'; @NgModule({ imports: [ ... StoreDevtoolsModule.instrument({ maxAge: 25, // Retains last 25 states logOnly: environment.production, // Restrict extension to log-only mode }), ], })
- Mengimpor
EffectsModule
keAppModule
Impor
EffectsModule
keAppModule
Anda dan daftar efek root Anda:import { EffectsModule } from '@ngrx/effects'; import { AppEffects } from './app.effects'; // Define your root effects @NgModule({ imports: [ ... EffectsModule.forRoot([AppEffects]), ], })
- Menentukan Reducer dan Efek Root
Buat berkas untuk reducer dan efek root Anda. Ini adalah titik awal untuk state dan logika sisi aplikasi Anda.
4. Membuat State, Aksi, Reducer, dan Efek Tingkat Tinggi
Sekarang setelah kita menyiapkan NgRx, mari kita definisikan state, aksi, reducer, dan efek tingkat tinggi untuk aplikasi kita. Ini adalah blok bangunan dasar manajemen state NgRx.
- Menentukan Antarmuka State
Tentukan antarmuka untuk state Anda. Antarmuka ini mendefinisikan struktur data yang akan disimpan di toko NgRx.
export interface AppState { counter: number; user: UserState; } export interface UserState { id: number | null; name: string | null; loggedIn: boolean; }
- Membuat Aksi
Aksi adalah peristiwa yang memicu perubahan state. Buat konstanta aksi untuk mendefinisikan jenis-jenis tindakan yang berbeda.
import { createAction, props } from '@ngrx/store'; export const increment = createAction('[Counter Component] Increment'); export const decrement = createAction('[Counter Component] Decrement'); export const reset = createAction('[Counter Component] Reset'); export const loadUser = createAction('[User API] Load User'); export const loadUserSuccess = createAction('[User API] Load User Success', props<{ user: UserState }>()); export const loadUserFailure = createAction('[User API] Load User Failure', props<{ error: string }>());
- Membuat Reducer
Reducer adalah fungsi murni yang menentukan bagaimana state berubah sebagai respons terhadap aksi. Gunakan fungsi
createReducer
untuk membuat reducer Anda.import { createReducer, on } from '@ngrx/store'; import { increment, decrement, reset } from './counter.actions'; export const initialState = 0; export const counterReducer = createReducer( initialState, on(increment, (state) => state + 1), on(decrement, (state) => state - 1), on(reset, (state) => 0) );
- Membuat Efek
Efek menangani efek samping, seperti panggilan API. Gunakan kelas
Actions
dancreateEffect
untuk membuat efek Anda.import { Injectable } from '@angular/core'; import { Actions, createEffect, ofType } from '@ngrx/effects'; import { map, mergeMap, catchError } from 'rxjs/operators'; import { of } from 'rxjs'; import { UserService } from './user.service'; import * as UserActions from './user.actions'; @Injectable() export class UserEffects { loadUser$ = createEffect(() => this.actions$.pipe( ofType(UserActions.loadUser), mergeMap(() => this.userService.getUser() .pipe( map(user => UserActions.loadUserSuccess({ user })), catchError(error => of(UserActions.loadUserFailure({ error: error.message }))) )) )); constructor(private actions$: Actions, private userService: UserService) {} }
- Menggabungkan Reducer
Jika Anda memiliki beberapa reducer, gabungkan mereka ke dalam reducer root tunggal menggunakan
combineReducers
:import { combineReducers } from '@ngrx/store'; import { counterReducer } from './counter.reducer'; import { userReducer } from './user.reducer'; export const reducers = combineReducers({ counter: counterReducer, user: userReducer, });
5. Menerapkan Manajemen State di Dalam Satu Modul
Sebelum kita mengatasi state lintas modul, mari kita lihat bagaimana menerapkan manajemen state dalam satu modul. Hal ini akan memberikan fondasi untuk menangani interaksi state yang lebih kompleks antar modul.
- Membuat Modul Fitur
Buat modul fitur untuk menginkapsulasi logika dan state yang terkait dengan fitur tertentu. Misalnya, kita dapat membuat modul
CounterModule
untuk mengelola state terkait penghitung.ng generate module counter
- Mengimpor
StoreModule.forFeature
Di dalam modul fitur Anda, impor
StoreModule.forFeature
dan konfigurasikan dengan reducer khusus fitur Anda. Ini membuat “cabang” state di dalam toko global.import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { StoreModule } from '@ngrx/store'; import { counterReducer } from './counter.reducer'; import { CounterComponent } from './counter.component'; @NgModule({ declarations: [CounterComponent], imports: [ CommonModule, StoreModule.forFeature('counter', counterReducer), ], exports: [CounterComponent], }) export class CounterModule {}
- Mengimpor
EffectsModule.forFeature
Mirip dengan
StoreModule.forFeature
, imporEffectsModule.forFeature
dan konfigurasikan dengan efek khusus fitur Anda. Ini mendaftarkan efek Anda dalam cakupan modul fitur.import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { StoreModule } from '@ngrx/store'; import { EffectsModule } from '@ngrx/effects'; import { CounterReducer } from './counter.reducer'; import { CounterComponent } from './counter.component'; import { CounterEffects } from './counter.effects'; @NgModule({ declarations: [CounterComponent], imports: [ CommonModule, StoreModule.forFeature('counter', CounterReducer), EffectsModule.forFeature([CounterEffects]) ], exports: [CounterComponent] }) export class CounterModule { }
- Menggunakan State dan Aksi di dalam Komponen
Suntikkan
Store
ke dalam komponen Anda dan gunakanselect
untuk mengambil state dandispatch
untuk memicu aksi.import { Component, OnInit } from '@angular/core'; import { Store } from '@ngrx/store'; import { Observable } from 'rxjs'; import { increment, decrement, reset } from './counter.actions'; @Component({ selector: 'app-counter', template: `
Counter: {{ counter$ | async }}
; constructor(private store: Store<{ counter: number }>) { this.counter$ = store.select('counter'); } ngOnInit() {} increment() { this.store.dispatch(increment()); } decrement() { this.store.dispatch(decrement()); } reset() { this.store.dispatch(reset()); } }
6. Berbagi State Antar Modul Menggunakan Selektor
Untuk berbagi state antar modul, kita menggunakan selektor. Selektor adalah fungsi murni yang mengambil bagian dari state toko. Dengan menentukan selektor di satu modul dan menggunakannya di modul lain, kita dapat secara efektif berbagi state tanpa langsung mengakses toko.
- Menentukan Selektor di Modul Asal
Di modul tempat state didefinisikan, buat selektor untuk memilih state tertentu.
import { createFeatureSelector, createSelector } from '@ngrx/store'; import { CounterState } from './counter.reducer'; export const selectCounterState = createFeatureSelector
('counter'); export const selectCounterValue = createSelector( selectCounterState, (state: CounterState) => state.value ); - Mengekspor Selektor
Ekspor selektor sehingga dapat digunakan di modul lain.
export * from './counter.selectors';
- Mengimpor dan Menggunakan Selektor di Modul Lain
Di modul tempat Anda perlu mengakses state, impor selektor dan gunakan dengan
store.select
.import { Component, OnInit } from '@angular/core'; import { Store } from '@ngrx/store'; import { Observable } from 'rxjs'; import { selectCounterValue } from '../counter/counter.selectors'; @Component({ selector: 'app-other', template: `
Counter Value: {{ counterValue$ | async }}
; constructor(private store: Store) { this.counterValue$ = store.select(selectCounterValue); } ngOnInit() {} } - Manfaat Selektor
- Enkapsulasi: Menyembunyikan struktur state internal.
- Reusable: Dapat digunakan di beberapa komponen dan modul.
- Dioptimalkan: NgRx dapat melakukan memoizing selektor untuk meningkatkan kinerja.
7. Memuat State Modul Secara Lambat dengan Efek
Dalam aplikasi besar, Anda mungkin ingin memuat state modul secara lambat untuk meningkatkan kinerja pemuatan awal. Hal ini dapat dilakukan dengan menggunakan Efek untuk memicu tindakan yang memuat state saat modul tertentu dimuat.
- Membuat Aksi untuk Memuat State Modul
Tentukan tindakan untuk memicu pemuatan state modul.
import { createAction } from '@ngrx/store'; export const loadModuleState = createAction('[MyModule] Load Module State'); export const loadModuleStateSuccess = createAction('[MyModule] Load Module State Success', props<{ data: any }>()); export const loadModuleStateFailure = createAction('[MyModule] Load Module State Failure', props<{ error: any }>());
- Membuat Efek untuk Memuat State
Buat efek yang mendengarkan tindakan
loadModuleState
, mengambil data yang diperlukan, dan memicu tindakan sukses atau gagal.import { Injectable } from '@angular/core'; import { Actions, createEffect, ofType } from '@ngrx/effects'; import { of } from 'rxjs'; import { switchMap, map, catchError } from 'rxjs/operators'; import { DataService } from './data.service'; import * as MyModuleActions from './my-module.actions'; @Injectable() export class MyModuleEffects { loadModuleState$ = createEffect(() => this.actions$.pipe( ofType(MyModuleActions.loadModuleState), switchMap(() => this.dataService.getData().pipe( map(data => MyModuleActions.loadModuleStateSuccess({ data })), catchError(error => of(MyModuleActions.loadModuleStateFailure({ error }))) )) )); constructor(private actions$: Actions, private dataService: DataService) {} }
- Memicu Aksi saat Modul Dimuat
Di dalam modul yang dimuat lambat, dispacth tindakan
loadModuleState
saat modul diinisialisasi.import { NgModule, OnInit } from '@angular/core'; import { Store } from '@ngrx/store'; import { loadModuleState } from './my-module.actions'; @NgModule({}) export class MyModule implements OnInit { constructor(private store: Store) {} ngOnInit() { this.store.dispatch(loadModuleState()); } }
8. Menggunakan Fitur Reducer untuk Modularitas yang Lebih Baik
Fitur Reducer adalah cara terorganisir untuk mengelola potongan state tertentu. Setiap fitur memiliki reducer-nya sendiri yang mengelola potongan state tertentu. Fitur Reducer ini dapat dimuat secara lambat dengan fitur modul.
- Mendefinisikan Fitur Reducer
import { createReducer, on } from '@ngrx/store'; import { EntityState, EntityAdapter, createEntityAdapter } from '@ngrx/entity'; import { Item } from './item.model'; import * as ItemActions from './item.actions'; export interface ItemState extends EntityState
- { loading: boolean; error: any; } export const itemAdapter: EntityAdapter
- = createEntityAdapter
- (); export const initialItemState: ItemState = itemAdapter.getInitialState({ loading: false, error: null }); export const itemReducer = createReducer( initialItemState, on(ItemActions.loadItems, state => ({ ...state, loading: true })), on(ItemActions.loadItemsSuccess, (state, { items }) => itemAdapter.setAll(items, { ...state, loading: false })), on(ItemActions.loadItemsFailure, (state, { error }) => ({ ...state, loading: false, error })) ); export const { selectIds, selectEntities, selectAll, selectTotal, } = itemAdapter.getSelectors();
- { loading: boolean; error: any; } export const itemAdapter: EntityAdapter
- Mendaftarkan Fitur Reducer di dalam Modul
import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { StoreModule } from '@ngrx/store'; import { EffectsModule } from '@ngrx/effects'; import { itemReducer } from './item.reducer'; import { ItemEffects } from './item.effects'; import { ItemListComponent } from './item-list.component'; @NgModule({ declarations: [ItemListComponent], imports: [ CommonModule, StoreModule.forFeature('items', itemReducer), EffectsModule.forFeature([ItemEffects]) ], exports: [ItemListComponent] }) export class ItemModule { }
- Menggunakan Selektor untuk mengakses state fitur
import { Component, OnInit } from '@angular/core'; import { Store, select } from '@ngrx/store'; import { Observable } from 'rxjs'; import { Item } from './item.model'; import * as ItemSelectors from './item.selectors'; @Component({ selector: 'app-item-list', template: `
- {{ item.name }}
- ; constructor(private store: Store) { this.items$ = this.store.pipe(select(ItemSelectors.selectAll)); } ngOnInit(): void { } }
9. Praktik Terbaik untuk Manajemen State NgRx Lintas Modul
Untuk memastikan manajemen state NgRx Anda yang efektif dan terorganisir dengan baik di seluruh modul, pertimbangkan praktik terbaik berikut:
- Definisikan State secara Jelas: Tentukan dengan jelas struktur state Anda dan hubungannya antar modul.
- Gunakan Selektor: Selalu gunakan selektor untuk mengakses dan berbagi state antar modul.
- Tetapkan Konvensi Aksi: Tetapkan konvensi yang konsisten untuk penamaan tindakan Anda.
- Modularisasi State: Atur state Anda ke dalam modul fitur untuk modularitas yang lebih baik.
- Gunakan Fitur Reducer: Menggunakan Fitur Reducer untuk organisasi yang lebih baik.
- Menangani Efek Samping dengan Hati-hati: Pastikan efek samping ditangani secara terpusat di Efek.
- Uji Reducer dan Efek: Tulis pengujian unit untuk reducer dan efek Anda untuk memastikan mereka berperilaku seperti yang diharapkan.
- Gunakan Naming Conventions yang Konsisten: Gunakan konvensi penamaan yang konsisten untuk aksi, reducer, selektor, dan efek Anda. Ini meningkatkan keterbacaan dan pemeliharaan kode.
- Dokumentasi: Dokumentasikan state, tindakan, reducer, dan efek Anda untuk membantu pengembang memahami bagaimana state dikelola.
10. Tips Pemecahan Masalah Umum NgRx
Meskipun NgRx memberikan pendekatan yang kuat untuk manajemen state, Anda mungkin menemui masalah. Berikut adalah beberapa tips pemecahan masalah umum:
- State Tidak Diperbarui:
- Pastikan reducer Anda mengembalikan salinan baru dari state alih-alih mengubah state yang ada.
- Verifikasi bahwa tindakan yang Anda kirimkan sesuai dengan yang ditangani oleh reducer Anda.
- Efek Tidak Dieksekusi:
- Pastikan efek Anda terdaftar di
EffectsModule
. - Periksa apakah tindakan yang efeknya mendengarkan dikirim dengan benar.
- Periksa error di dalam efek Anda yang mungkin mencegahnya dijalankan.
- Pastikan efek Anda terdaftar di
- Masalah Kinerja:
- Gunakan selektor untuk hanya memilih potongan state yang diperlukan.
- Hindari perhitungan yang kompleks di dalam reducer.
- Pertimbangkan untuk menggunakan memoizing selektor untuk meningkatkan kinerja.
- Menggunakan
StoreDevtools
:- Gunakan ekstensi browser NgRx Devtools untuk memeriksa state, tindakan, dan perubahan state Anda.
- Gunakan fitur time-travel debugging untuk menelusuri riwayat state aplikasi Anda.
- Kesalahan Reducer Tidak Murni:
- Reducer harus menjadi fungsi murni, yang berarti mereka tidak boleh memiliki efek samping atau bergantung pada state eksternal.
- Periksa reducer Anda untuk operasi yang mengubah state atau bergantung pada sumber daya eksternal.
11. Kesimpulan: Kekuatan NgRx dalam Aplikasi Angular Modular
NgRx memberikan solusi yang kuat dan terstruktur untuk manajemen state dalam aplikasi Angular modular. Dengan memahami komponen inti NgRx—state, tindakan, reducer, efek, dan selektor—dan menerapkan praktik terbaik, Anda dapat membangun aplikasi yang skalabel, dapat dipelihara, dan mudah didebug. Manajemen state lintas modul memastikan bahwa komponen yang berbeda dalam aplikasi Anda dapat berbagi dan memodifikasi state secara konsisten dan efisien. Gunakan teknik-teknik yang dijelaskan dalam artikel ini untuk meningkatkan organisasi dan ketahanan aplikasi Angular Anda.
Dengan berinvestasi dalam NgRx, Anda memberdayakan tim Anda untuk membangun aplikasi Angular yang lebih kokoh dan dapat diprediksi. Jadi, mulailah menjelajahi dan menerapkan NgRx untuk meningkatkan manajemen state aplikasi Angular Anda hari ini!
“`