Pola NgRx Tingkat Lanjut untuk Aplikasi Angular Enterprise
NgRx adalah pustaka manajemen state yang kuat dan populer untuk aplikasi Angular.
Ini menerapkan pola arsitektur Redux, menyediakan cara terpusat dan prediktif untuk
mengelola state aplikasi Anda. Dalam aplikasi kecil, NgRx mungkin terasa berlebihan,
tetapi untuk aplikasi enterprise yang kompleks, NgRx dapat menjadi pengubah permainan,
membantu Anda menjaga kode yang bersih, terawat, dan mudah diuji.
Dalam posting blog ini, kita akan menjelajahi pola NgRx tingkat lanjut yang dapat
membantu Anda membangun aplikasi Angular enterprise yang lebih terukur dan mudah
dipelihara. Kita akan membahas berbagai topik, termasuk pemilihan entity, efek,
penanganan kesalahan, dan pengujian.
Daftar Isi
- Pendahuluan
- Mengapa Menggunakan NgRx dalam Aplikasi Enterprise?
- Pola Dasar NgRx: Pengingat Singkat
- Pemilihan Entity Tingkat Lanjut
- Efek NgRx Tingkat Lanjut
- Penanganan Kesalahan Tingkat Lanjut dengan NgRx
- Pemfaktoran Kode NgRx untuk Skalabilitas
- Pengujian Lanjutan Komponen dan Efek NgRx
- Strategi Debu dan Optimalisasi Kinerja
- Alat dan Ekstensi NgRx
- Praktik Terbaik untuk Aplikasi Angular Enterprise dengan NgRx
- Kesimpulan
1. Pendahuluan
Aplikasi Angular enterprise seringkali kompleks dan melibatkan pengelolaan data yang
signifikan. NgRx menawarkan solusi yang terstruktur dan prediktif untuk mengelola
state aplikasi, sehingga memudahkan untuk memahami, memelihara, dan menguji kode.
Dalam posting blog ini, kita akan membahas pola NgRx tingkat lanjut yang melampaui
dasar-dasar dan mengatasi tantangan yang unik dalam aplikasi enterprise. Kita akan
menjelajahi teknik untuk pemilihan data yang efisien, penanganan efek yang kuat,
penanganan kesalahan yang komprehensif, dan strategi untuk membuat kode NgRx Anda
lebih terukur dan mudah dipelihara.
2. Mengapa Menggunakan NgRx dalam Aplikasi Enterprise?
NgRx membawa sejumlah manfaat ke aplikasi Angular enterprise:
-
Manajemen State Terpusat: NgRx menyediakan penyimpanan state tunggal
untuk seluruh aplikasi Anda, sehingga memudahkan untuk memahami dan mengelola state. -
Prediktabilitas: State diperbarui dengan cara yang prediktif dan
terkontrol, melalui reduksi. Ini membuat debugging dan pengujian lebih mudah. -
Keterukuran: NgRx membantu Anda mengatur kode Anda dengan cara yang
modular dan terukur. -
Kemudahan Pemeliharaan: NgRx membuat kode Anda lebih mudah dipelihara
dengan menyediakan cara yang jelas dan terstruktur untuk mengelola state aplikasi. -
Kemudahan Pengujian: Arsitektur NgRx membuatnya mudah untuk
melakukan unit testing terhadap reduksi dan efek Anda. -
Perfoma: Dengan teknik seperti memoization, NgRx dapat dioptimalkan
untuk performa bahkan dalam aplikasi kompleks.
3. Pola Dasar NgRx: Pengingat Singkat
Sebelum masuk ke pola tingkat lanjut, mari kita lakukan pengingat cepat tentang
konsep dasar NgRx:
-
State: Data aplikasi Anda. Ini adalah sumber kebenaran tunggal untuk
aplikasi Anda. -
Aksi: Objek JavaScript sederhana yang mewakili niat untuk mengubah
state. -
Reduksi: Fungsi murni yang mengambil state dan aksi saat ini sebagai
input dan mengembalikan state baru. Reduksi menentukan bagaimana state berubah sebagai
respons terhadap aksi. -
Efek: Sisi-efek yang mendengarkan aksi dan melakukan tugas-tugas
seperti panggilan API, navigasi, dan interaksi lain dengan dunia luar. -
Pemilih: Fungsi yang digunakan untuk mengekstrak potongan data dari state.
Pemilih dapat menggunakan memoization untuk meningkatkan performa. -
Toko (Store): Sumber kebenaran tunggal yang menyimpan state aplikasi.
Komponen berinteraksi dengan store dengan mengirim aksi dan memilih data.
4. Pemilihan Entity Tingkat Lanjut
Pemilihan entity adalah aspek penting dari aplikasi NgRx. Ini melibatkan pemilihan
potongan data tertentu dari state store. Teknik pemilihan yang efisien dapat
memperbaiki performa dan mengurangi rendering yang tidak perlu.
4.1. Pemilihan Entity Dasar
Pendekatan paling dasar adalah menggunakan pemilih sederhana untuk mengambil
sepotong state tertentu. Misalnya:
// Selector dasar
export const selectTodosState = createFeatureSelector<TodosState>('todos');
export const selectAllTodos = createSelector(
selectTodosState,
(state: TodosState) => state.todos
);
4.2. Pemilihan dengan Memoization
Memoization adalah teknik pengoptimalan yang menyimpan hasil fungsi dan mengembalikan
hasil yang di-cache ketika input yang sama terjadi lagi. Ini dapat secara signifikan
meningkatkan performa pemilih, terutama untuk perhitungan yang kompleks.
import { createSelector } from '@ngrx/store';
import { Todo } from './models/todo.model';
export const selectCompletedTodos = createSelector(
selectAllTodos,
(todos: Todo[]) => todos.filter(todo => todo.completed)
);
Dalam contoh ini, selectCompletedTodos
hanya akan menghitung ulang
jika selectAllTodos
mengembalikan array baru. Jika tidak, ia akan
mengembalikan hasil yang di-cache.
4.3. Pemilihan Properti dan Transformasi
Anda dapat membuat pemilih yang tidak hanya memilih data tetapi juga mengubahnya.
Ini sangat berguna untuk memformat data untuk tampilan atau untuk menghitung nilai
yang diturunkan.
export const selectTodoCount = createSelector(
selectAllTodos,
(todos: Todo[]) => todos.length
);
export const selectCompletedTodoCount = createSelector(
selectCompletedTodos,
(todos: Todo[]) => todos.length
);
4.4. Pemilihan Relasional
Dalam aplikasi enterprise, seringkali Anda memiliki data relasional. NgRx Entity
memberikan cara yang nyaman untuk mengelola dan memilih data entitas.
import { createEntityAdapter, EntityAdapter, EntityState } from '@ngrx/entity';
export interface TodoState extends EntityState<Todo> {
// tambahkan properti state spesifik entitas di sini
loading: boolean;
}
export const adapter: EntityAdapter<Todo> = createEntityAdapter<Todo>();
export const initialState: TodoState = adapter.getInitialState({
// setel state awal spesifik entitas di sini
loading: false
});
export const {
selectIds,
selectEntities,
selectAll,
selectTotal,
} = adapter.getSelectors();
export const selectAllTodos = createSelector(
selectTodosState,
selectAll
);
export const selectTodoEntities = createSelector(
selectTodosState,
selectEntities
);
export const selectTodoById = (id: string) => createSelector(
selectTodoEntities,
entities => entities[id]
);
NgRx Entity menyediakan berbagai pemilih bawaan untuk mengambil data entitas.
Anda juga dapat membuat pemilih khusus untuk persyaratan spesifik Anda.
5. Efek NgRx Tingkat Lanjut
Efek digunakan untuk menangani efek samping dalam aplikasi NgRx Anda, seperti membuat
permintaan HTTP, berinteraksi dengan localStorage, atau menavigasi ke halaman lain.
5.1. Membatalkan Permintaan HTTP
Dalam beberapa skenario, Anda mungkin perlu membatalkan permintaan HTTP yang sedang
berlangsung. Ini dapat dilakukan menggunakan operator RxJS seperti `takeUntil` atau
`switchMap`.
import { Injectable } from '@angular/core';
import { Actions, createEffect, ofType } from '@ngrx/effects';
import { of } from 'rxjs';
import { catchError, map, switchMap, takeUntil } from 'rxjs/operators';
import { HttpClient } from '@angular/common/http';
import * as TodoActions from './todo.actions';
@Injectable()
export class TodoEffects {
loadTodos$ = createEffect(() => this.actions$.pipe(
ofType(TodoActions.loadTodos),
switchMap(() => this.http.get<Todo[]>('/api/todos').pipe(
map(todos => TodoActions.loadTodosSuccess({ todos })),
catchError(error => of(TodoActions.loadTodosFailure({ error })))
))
));
// Membatalkan efek
stopLoadTodos$ = createEffect(() => this.actions$.pipe(
ofType(TodoActions.stopLoadTodos),
map(() => TodoActions.clearTodos()) // Contoh aksi untuk membersihkan state
));
loadTodosWithCancellation$ = createEffect(() => this.actions$.pipe(
ofType(TodoActions.loadTodosWithCancellation),
switchMap(() => this.http.get<Todo[]>('/api/todos').pipe(
takeUntil(this.actions$.pipe(ofType(TodoActions.stopLoadTodos))),
map(todos => TodoActions.loadTodosSuccess({ todos })),
catchError(error => of(TodoActions.loadTodosFailure({ error })))
))
));
constructor(private actions$: Actions, private http: HttpClient) {}
}
Dalam contoh ini, `loadTodosWithCancellation$` membatalkan permintaan HTTP jika aksi
`stopLoadTodos` dikirimkan.
5.2. Menangani Berbagai Aksi dengan Operator
Efek dapat mendengarkan berbagai aksi menggunakan operator RxJS seperti
`mergeMap` atau `exhaustMap`.
loadTodosAndUsers$ = createEffect(() => this.actions$.pipe(
ofType(TodoActions.loadTodos, UserActions.loadUsers),
mergeMap(() => {
const todos$ = this.http.get<Todo[]>('/api/todos');
const users$ = this.http.get<User[]>('/api/users');
return forkJoin([todos$, users$]).pipe(
map(([todos, users]) => {
return TodoActions.loadTodosAndUsersSuccess({ todos, users });
}),
catchError(error => of(TodoActions.loadTodosAndUsersFailure({ error })))
);
})
));
Dalam contoh ini, `loadTodosAndUsers$` dipicu oleh aksi `loadTodos` atau
`loadUsers`. Ini membuat permintaan HTTP paralel untuk data todos dan pengguna dan
kemudian menggabungkan hasilnya.
5.3. Menggabungkan Aksi
Dalam beberapa kasus, Anda mungkin perlu mengirimkan beberapa aksi dari satu efek.
Ini dapat dilakukan menggunakan operator RxJS seperti `concatMap` atau
`exhaustMap`.
createTodo$ = createEffect(() => this.actions$.pipe(
ofType(TodoActions.createTodo),
concatMap(action => this.http.post<Todo>('/api/todos', action.todo).pipe(
map(todo => [
TodoActions.createTodoSuccess({ todo }),
NotificationActions.showSuccess({ message: 'Todo dibuat!' })
]),
concatAll(), // Menggabungkan array aksi menjadi aliran aksi
catchError(error => of(TodoActions.createTodoFailure({ error })))
))
));
Dalam contoh ini, `createTodo$` mengirimkan aksi `createTodoSuccess` dan
`showSuccess` setelah todo berhasil dibuat.
5.4. Menggunakan createEffect alih-alih @Effect
Meskipun `@Effect` sebelumnya merupakan cara standar untuk membuat efek, `createEffect`
direkomendasikan untuk sintaks yang lebih jelas dan mudah dipahami.
Menggunakan `@Effect` (tidak direkomendasikan):
import { Injectable } from '@angular/core';
import { Actions, Effect, ofType } from '@ngrx/effects';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
@Injectable()
export class TodoEffects {
@Effect()
loadTodos$: Observable<any> = this.actions$.pipe(
ofType('[Todo] Load Todos'),
map(() => ({ type: '[Todo] Load Todos Success' }))
);
constructor(private actions$: Actions) {}
}
Menggunakan `createEffect` (direkomendasikan):
import { Injectable } from '@angular/core';
import { Actions, createEffect, ofType } from '@ngrx/effects';
import { map } from 'rxjs/operators';
@Injectable()
export class TodoEffects {
loadTodos$ = createEffect(() => this.actions$.pipe(
ofType('[Todo] Load Todos'),
map(() => ({ type: '[Todo] Load Todos Success' }))
));
constructor(private actions$: Actions) {}
}
`createEffect` memaksa Anda untuk mengembalikan aliran, membuat alur data eksplisit
dan lebih mudah diikuti.
6. Penanganan Kesalahan Tingkat Lanjut dengan NgRx
Penanganan kesalahan sangat penting dalam aplikasi enterprise. NgRx menyediakan
beberapa cara untuk menangani kesalahan dengan anggun.
6.1. Middleware Penanganan Kesalahan Global
Anda dapat membuat middleware penanganan kesalahan global yang mencegat semua aksi
yang gagal dan menangani kesalahan tersebut secara terpusat.
import { Injectable } from '@angular/core';
import { Actions, createEffect, ofType } from '@ngrx/effects';
import { of } from 'rxjs';
import { catchError } from 'rxjs/operators';
@Injectable()
export class GlobalErrorEffects {
constructor(private actions$: Actions) {}
globalError$ = createEffect(() => this.actions$.pipe(
ofType(
// cantumkan semua aksi kegagalan di sini
'[Todo] Load Todos Failure',
'[Todo] Create Todo Failure'
),
catchError((error, caught) => {
console.error('Aksi Gagal:', error);
// kirim aksi untuk menampilkan pesan kesalahan global
return of({ type: '[Global] Show Error', error });
})
));
}
Middleware ini mencegat semua aksi yang gagal dan mencatat kesalahan ke konsol.
Anda juga dapat mengirim aksi untuk menampilkan pesan kesalahan global kepada pengguna.
6.2. Aksi Kesalahan Spesifik
Cara lain untuk menangani kesalahan adalah dengan mengirimkan aksi kesalahan spesifik
ketika terjadi kesalahan. Ini memungkinkan Anda menangani kesalahan secara berbeda
tergantung pada aksi yang gagal.
// dalam efek Anda
loadTodos$ = createEffect(() => this.actions$.pipe(
ofType('[Todo] Load Todos'),
switchMap(() => this.http.get<Todo[]>('/api/todos').pipe(
map(todos => ({ type: '[Todo] Load Todos Success', todos })),
catchError(error => of({ type: '[Todo] Load Todos Failure', error }))
))
));
Dalam contoh ini, aksi `[Todo] Load Todos Failure` dikirimkan ketika permintaan
HTTP gagal. Anda kemudian dapat menangani aksi ini di reducer Anda dan memperbarui
state yang sesuai.
6.3. Logika Coba Lagi (Retry Logic)
Dalam beberapa kasus, Anda mungkin ingin mencoba lagi permintaan yang gagal secara
otomatis. Ini dapat dilakukan menggunakan operator RxJS seperti `retry` atau
`retryWhen`.
import { retryWhen, delay, take } from 'rxjs/operators';
loadTodos$ = createEffect(() => this.actions$.pipe(
ofType('[Todo] Load Todos'),
switchMap(() => this.http.get<Todo[]>('/api/todos').pipe(
retryWhen(errors => errors.pipe(delay(1000), take(3))), // Coba lagi hingga 3 kali dengan penundaan 1 detik
map(todos => ({ type: '[Todo] Load Todos Success', todos })),
catchError(error => of({ type: '[Todo] Load Todos Failure', error }))
))
));
Dalam contoh ini, permintaan HTTP akan dicoba lagi hingga 3 kali dengan penundaan
1 detik antara setiap percobaan.
7. Pemfaktoran Kode NgRx untuk Skalabilitas
Saat aplikasi Anda bertambah besar, penting untuk memfaktorkan kode NgRx Anda agar
tetap teratur dan mudah dipelihara.
7.1. Modul Fitur (Feature Modules)
Salah satu cara untuk memfaktorkan kode NgRx Anda adalah dengan menggunakan modul
fitur. Modul fitur adalah cara untuk mengelompokkan state, aksi, reduksi, efek, dan
pemilih terkait menjadi satu modul yang koheren.
// todo.module.ts
import { NgModule } from '@angular/core';
import { StoreModule } from '@ngrx/store';
import { EffectsModule } from '@ngrx/effects';
import { todoReducer } from './todo.reducer';
import { TodoEffects } from './todo.effects';
@NgModule({
imports: [
StoreModule.forFeature('todos', todoReducer),
EffectsModule.forFeature([TodoEffects])
]
})
export class TodoModule {}
Dalam contoh ini, modul fitur `TodoModule` berisi semua kode NgRx terkait dengan
fitur todos.
7.2. Berbagi Kode Antar Modul
Dalam beberapa kasus, Anda mungkin perlu berbagi kode antar modul fitur yang berbeda.
Ini dapat dilakukan dengan membuat modul bersama yang berisi kode umum.
// shared.module.ts
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
@NgModule({
imports: [CommonModule],
exports: [CommonModule]
})
export class SharedModule {}
Dalam contoh ini, modul bersama `SharedModule` berisi modul `CommonModule`, yang
diperlukan oleh banyak komponen.
7.3. Abstraksi Redux
Untuk aplikasi skala enterprise, Anda mungkin ingin lebih abstrakkan interaksi langsung
dengan Redux. Ini dapat dilakukan dengan memperkenalkan layanan pembantu atau abstraksi
yang merangkum aksi pengiriman dan pemilihan state.
// todo.service.ts
import { Injectable } from '@angular/core';
import { Store } from '@ngrx/store';
import * as TodoActions from './todo.actions';
import * as TodoSelectors from './todo.selectors';
import { Todo } from './models/todo.model';
@Injectable({
providedIn: 'root'
})
export class TodoService {
constructor(private store: Store) {}
loadTodos(): void {
this.store.dispatch(TodoActions.loadTodos());
}
getTodos(): Observable<Todo[]> {
return this.store.select(TodoSelectors.selectAllTodos);
}
createTodo(todo: Todo): void {
this.store.dispatch(TodoActions.createTodo({ todo }));
}
// Metode tambahan untuk memperbarui, menghapus, dll.
}
Ini menyediakan lapisan abstraksi di atas implementasi NgRx, sehingga memudahkan untuk
berubah atau meningkatkan kode NgRx tanpa memengaruhi komponen Anda secara langsung.
8. Pengujian Lanjutan Komponen dan Efek NgRx
Pengujian sangat penting untuk memastikan bahwa kode NgRx Anda berfungsi dengan benar.
8.1. Unit Testing Reduksi
Unit testing reduksi melibatkan pengujian bahwa reduksi Anda menghasilkan state yang
benar untuk aksi tertentu.
import { todoReducer, initialState } from './todo.reducer';
import * as TodoActions from './todo.actions';
import { Todo } from './models/todo.model';
describe('Todo Reducer', () => {
it('harus mengembalikan state awal', () => {
const action = {} as any;
const state = todoReducer(undefined, action);
expect(state).toBe(initialState);
});
it('harus memuat todos', () => {
const todos: Todo[] = [{ id: '1', name: 'Todo 1', completed: false }];
const action = TodoActions.loadTodosSuccess({ todos });
const state = todoReducer(initialState, action);
expect(state.entities['1']).toEqual(todos[0]);
});
});
Dalam contoh ini, kita menguji bahwa reduksi menghasilkan state awal yang benar dan
bahwa ia memuat todos dengan benar.
8.2. Integrasi Efek
Integrasi efek melibatkan pengujian bahwa efek Anda mengirimkan aksi yang benar dan
bahwa mereka menangani kesalahan dengan benar.
import { TestBed } from '@angular/core/testing';
import { provideMockActions } from '@ngrx/effects/testing';
import { Observable, of } from 'rxjs';
import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
import { TodoEffects } from './todo.effects';
import * as TodoActions from './todo.actions';
import { Todo } from './models/todo.model';
describe('Todo Effects', () => {
let actions$: Observable<any>;
let effects: TodoEffects;
let httpTestingController: HttpTestingController;
beforeEach(() => {
TestBed.configureTestingModule({
imports: [HttpClientTestingModule],
providers: [
TodoEffects,
provideMockActions(() => actions$)
]
});
effects = TestBed.inject(TodoEffects);
httpTestingController = TestBed.inject(HttpTestingController);
});
it('harus memuat todos', () => {
const todos: Todo[] = [{ id: '1', name: 'Todo 1', completed: false }];
const action = TodoActions.loadTodos();
actions$ = of(action);
effects.loadTodos$.subscribe(result => {
expect(result).toEqual(TodoActions.loadTodosSuccess({ todos }));
});
const req = httpTestingController.expectOne('/api/todos');
expect(req.request.method).toBe('GET');
req.flush(todos);
});
});
Dalam contoh ini, kita menguji bahwa efek memuat todos dengan benar dan bahwa ia
mengirimkan aksi `loadTodosSuccess` yang benar.
8.3. End-to-End Testing
End-to-end testing melibatkan pengujian bahwa seluruh aplikasi berfungsi dengan benar.
Ini dapat dilakukan menggunakan alat seperti Cypress atau Selenium.
End-to-end testing tidak spesifik untuk NgRx, tetapi penting untuk memastikan bahwa
aplikasi Anda berfungsi dengan benar secara keseluruhan.
9. Strategi Debu dan Optimalisasi Kinerja
Dengan aplikasi enterprise, penting untuk mengoptimalkan performa dan memastikan
aplikasi responsif.
9.1. Debounce Time
`debounceTime` adalah operator RxJS yang dapat digunakan untuk membatasi frekuensi
aksi dikirimkan. Ini dapat berguna untuk meningkatkan performa saat pengguna mengetik
di kolom pencarian atau melakukan tindakan lain yang memicu banyak aksi.
searchTodos$ = createEffect(() => this.actions$.pipe(
ofType(TodoActions.searchTodos),
debounceTime(300), // Tunggu 300ms setelah ketikan terakhir
switchMap(action => this.http.get<Todo[]>(`/api/todos?q=${action.query}`).pipe(
map(todos => TodoActions.searchTodosSuccess({ todos })),
catchError(error => of(TodoActions.searchTodosFailure({ error })))
))
));
Dalam contoh ini, `searchTodos$` hanya membuat permintaan HTTP setelah pengguna berhenti
mengetik selama 300ms.
9.2. Memoization Lanjutan
Selain memoization dasar, Anda dapat menggunakan pustaka seperti `reselect` untuk
memoization yang lebih kompleks. `reselect` memungkinkan Anda membuat pemilih yang
dapat memoize hasil dari berbagai input.
import { createSelector } from 'reselect';
const selectTodos = (state: AppState) => state.todos.todos;
const selectQuery = (state: AppState) => state.todos.query;
export const selectFilteredTodos = createSelector(
selectTodos,
selectQuery,
(todos, query) => todos.filter(todo => todo.name.includes(query))
);
`reselect` secara efisien me-memoize pemilih berdasarkan input mereka, yang
meminimalkan perhitungan yang tidak perlu.
9.3. Kompresi State
Untuk state yang sangat besar, pertimbangkan untuk mengompres state sebelum
menyimpannya ke localStorage atau mengirimkannya melalui jaringan. Ini dapat mengurangi
ukuran state secara signifikan dan meningkatkan performa.
import { compress, decompress } from 'lz-string';
// Mengompres state
const compressedState = compress(JSON.stringify(state));
// Mendekompres state
const decompressedState = JSON.parse(decompress(compressedState));
10. Alat dan Ekstensi NgRx
Ada beberapa alat dan ekstensi yang dapat membantu Anda mengembangkan aplikasi NgRx
Anda.
10.1. Alur NgRx
Alur NgRx adalah alat baris perintah yang dapat membantu Anda membuat kode NgRx yang
boilerplate.
ng generate @ngrx/schematics:store State --module app.module.ts --statePath app.state.ts
Alur NgRx dapat menghemat banyak waktu dan upaya dengan menghasilkan kode yang
diperlukan untuk state, aksi, reduksi, efek, dan pemilih Anda.
10.2. DevTools
NgRx DevTools adalah ekstensi browser yang memungkinkan Anda memeriksa state NgRx
Anda, mengirimkan aksi, dan memutar ulang perubahan state.
NgRx DevTools adalah alat yang tak ternilai harganya untuk debugging dan memahami
aplikasi NgRx Anda.
10.3. Ekstensi NgRx
Ada beberapa ekstensi NgRx yang dapat membantu Anda mengembangkan aplikasi NgRx Anda.
Beberapa ekstensi populer meliputi:
-
NgRx Data: Menyediakan cara yang mudah dan terstruktur untuk
mengelola data di aplikasi NgRx Anda. -
NgRx Entity: Memungkinkan Anda mengelola koleksi entitas secara
efisien. - NgRx Router: Menawarkan integrasi yang lancar dengan router Angular.
11. Praktik Terbaik untuk Aplikasi Angular Enterprise dengan NgRx
Berikut adalah beberapa praktik terbaik untuk menggunakan NgRx dalam aplikasi Angular
enterprise: