
Sampai saat ini, tidak ada satu pun aplikasi 
SPA besar yang lengkap tanpa 
manajemen negara . Ada beberapa solusi untuk 
Angular di bidang ini. Yang paling populer adalah 
NgRx . Ini mengimplementasikan pola 
Redux menggunakan perpustakaan 
RxJs dan memiliki alat yang baik.
Pada artikel ini, kita akan 
membahas modul 
NgRx utama secara 
singkat dan fokus lebih detail pada 
pustaka sudut-ngrx-data , yang memungkinkan Anda untuk membuat 
CRUD penuh dengan 
manajemen negara dalam lima menit.
Ulasan NgRx
Anda dapat membaca lebih lanjut tentang 
NgRx di artikel berikut:
- 
Aplikasi reaktif pada Angular / NGRX. Bagian 1. Pendahuluan- 
Aplikasi reaktif pada Angular / NGRX. Bagian 2. Toko- 
Aplikasi reaktif pada Angular / NGRX. Bagian 3. EfekSecara singkat pertimbangkan modul utama 
NgRx , pro dan kontra.
NgRx / store - mengimplementasikan pola Redux.
Implementasi toko sederhanacounter.actions.ts
export const INCREMENT = 'INCREMENT'; export const DECREMENT = 'DECREMENT'; export const RESET = 'RESET'; 
counter.reducer.ts
 import { Action } from '@ngrx/store'; const initialState = 0; export function counterReducer(state: number = initialState, action: Action) { switch (action.type) { case INCREMENT: return state + 1; case DECREMENT: return state - 1; case RESET: return 0; default: return state; } } 
.
Koneksi ke modul
 import { NgModule } from '@angular/core'; import { StoreModule } from '@ngrx/store'; import { counterReducer } from './counter'; @NgModule({ imports: [StoreModule.forRoot({ count: counterReducer })], }) export class AppModule {} 
Gunakan dalam komponen
 import { Component } from '@angular/core'; import { Store, select } from '@ngrx/store'; import { Observable } from 'rxjs'; import { INCREMENT, DECREMENT, RESET } from './counter'; interface AppState { count: number; } @Component({ selector: 'app-my-counter', template: ` <button (click)="increment()">Increment</button> <div>Current Count: {{ count$ | async }}</div> <button (click)="decrement()">Decrement</button> <button (click)="reset()">Reset Counter</button> `, }) export class MyCounterComponent { count$: Observable<number>; constructor(private store: Store<AppState>) { this.count$ = store.pipe(select('count')); } increment() { this.store.dispatch({ type: INCREMENT }); } decrement() { this.store.dispatch({ type: DECREMENT }); } reset() { this.store.dispatch({ type: RESET }); } } 
 NgRx / store-devtools - memungkinkan Anda untuk melacak perubahan dalam aplikasi melalui 
redux-devtools .
Contoh Koneksi import { StoreDevtoolsModule } from '@ngrx/store-devtools'; @NgModule({ imports: [ StoreModule.forRoot(reducers), //      StoreModule StoreDevtoolsModule.instrument({ maxAge: 25, //   25  }), ], }) export class AppModule {} 
 NgRx / efek - memungkinkan Anda untuk menambahkan data yang masuk ke aplikasi ke repositori, seperti permintaan http.
Contoh./effects/auth.effects.ts
 import { Injectable } from '@angular/core'; import { HttpClient } from '@angular/common/http'; import { Action } from '@ngrx/store'; import { Actions, Effect, ofType } from '@ngrx/effects'; import { Observable, of } from 'rxjs'; import { catchError, map, mergeMap } from 'rxjs/operators'; @Injectable() export class AuthEffects { // Listen for the 'LOGIN' action @Effect() login$: Observable<Action> = this.actions$.pipe( ofType('LOGIN'), mergeMap(action => this.http.post('/auth', action.payload).pipe( // If successful, dispatch success action with result map(data => ({ type: 'LOGIN_SUCCESS', payload: data })), // If request fails, dispatch failed action catchError(() => of({ type: 'LOGIN_FAILED' })) ) ) ); constructor(private http: HttpClient, private actions$: Actions) {} } 
Menghubungkan efek ke modul
 import { EffectsModule } from '@ngrx/effects'; import { AuthEffects } from './effects/auth.effects'; @NgModule({ imports: [EffectsModule.forRoot([AuthEffects])], }) export class AppModule {} 
 NgRx / entitas - menyediakan kemampuan untuk bekerja dengan array data.
Contohuser.model.ts
 export interface User { id: string; name: string; } 
user.actions.ts
 import { Action } from '@ngrx/store'; import { Update } from '@ngrx/entity'; import { User } from './user.model'; export enum UserActionTypes { LOAD_USERS = '[User] Load Users', ADD_USER = '[User] Add User', UPSERT_USER = '[User] Upsert User', ADD_USERS = '[User] Add Users', UPSERT_USERS = '[User] Upsert Users', UPDATE_USER = '[User] Update User', UPDATE_USERS = '[User] Update Users', DELETE_USER = '[User] Delete User', DELETE_USERS = '[User] Delete Users', CLEAR_USERS = '[User] Clear Users', } export class LoadUsers implements Action { readonly type = UserActionTypes.LOAD_USERS; constructor(public payload: { users: User[] }) {} } export class AddUser implements Action { readonly type = UserActionTypes.ADD_USER; constructor(public payload: { user: User }) {} } export class UpsertUser implements Action { readonly type = UserActionTypes.UPSERT_USER; constructor(public payload: { user: User }) {} } export class AddUsers implements Action { readonly type = UserActionTypes.ADD_USERS; constructor(public payload: { users: User[] }) {} } export class UpsertUsers implements Action { readonly type = UserActionTypes.UPSERT_USERS; constructor(public payload: { users: User[] }) {} } export class UpdateUser implements Action { readonly type = UserActionTypes.UPDATE_USER; constructor(public payload: { user: Update<User> }) {} } export class UpdateUsers implements Action { readonly type = UserActionTypes.UPDATE_USERS; constructor(public payload: { users: Update<User>[] }) {} } export class DeleteUser implements Action { readonly type = UserActionTypes.DELETE_USER; constructor(public payload: { id: string }) {} } export class DeleteUsers implements Action { readonly type = UserActionTypes.DELETE_USERS; constructor(public payload: { ids: string[] }) {} } export class ClearUsers implements Action { readonly type = UserActionTypes.CLEAR_USERS; } export type UserActionsUnion = | LoadUsers | AddUser | UpsertUser | AddUsers | UpsertUsers | UpdateUser | UpdateUsers | DeleteUser | DeleteUsers | ClearUsers; 
user.reducer.ts
 import { EntityState, EntityAdapter, createEntityAdapter } from '@ngrx/entity'; import { User } from './user.model'; import { UserActionsUnion, UserActionTypes } from './user.actions'; export interface State extends EntityState<User> { // additional entities state properties selectedUserId: number | null; } export const adapter: EntityAdapter<User> = createEntityAdapter<User>(); export const initialState: State = adapter.getInitialState({ // additional entity state properties selectedUserId: null, }); export function reducer(state = initialState, action: UserActionsUnion): State { switch (action.type) { case UserActionTypes.ADD_USER: { return adapter.addOne(action.payload.user, state); } case UserActionTypes.UPSERT_USER: { return adapter.upsertOne(action.payload.user, state); } case UserActionTypes.ADD_USERS: { return adapter.addMany(action.payload.users, state); } case UserActionTypes.UPSERT_USERS: { return adapter.upsertMany(action.payload.users, state); } case UserActionTypes.UPDATE_USER: { return adapter.updateOne(action.payload.user, state); } case UserActionTypes.UPDATE_USERS: { return adapter.updateMany(action.payload.users, state); } case UserActionTypes.DELETE_USER: { return adapter.removeOne(action.payload.id, state); } case UserActionTypes.DELETE_USERS: { return adapter.removeMany(action.payload.ids, state); } case UserActionTypes.LOAD_USERS: { return adapter.addAll(action.payload.users, state); } case UserActionTypes.CLEAR_USERS: { return adapter.removeAll({ ...state, selectedUserId: null }); } default: { return state; } } } export const getSelectedUserId = (state: State) => state.selectedUserId; // get the selectors const { selectIds, selectEntities, selectAll, selectTotal } = adapter.getSelectors(); // select the array of user ids export const selectUserIds = selectIds; // select the dictionary of user entities export const selectUserEntities = selectEntities; // select the array of users export const selectAllUsers = selectAll; // select the total user count export const selectUserTotal = selectTotal; 
reduksi / index.ts
 import { createSelector, createFeatureSelector, ActionReducerMap, } from '@ngrx/store'; import * as fromUser from './user.reducer'; export interface State { users: fromUser.State; } export const reducers: ActionReducerMap<State> = { users: fromUser.reducer, }; export const selectUserState = createFeatureSelector<fromUser.State>('users'); export const selectUserIds = createSelector( selectUserState, fromUser.selectUserIds ); export const selectUserEntities = createSelector( selectUserState, fromUser.selectUserEntities ); export const selectAllUsers = createSelector( selectUserState, fromUser.selectAllUsers ); export const selectUserTotal = createSelector( selectUserState, fromUser.selectUserTotal ); export const selectCurrentUserId = createSelector( selectUserState, fromUser.getSelectedUserId ); export const selectCurrentUser = createSelector( selectUserEntities, selectCurrentUserId, (userEntities, userId) => userEntities[userId] ); 
 Apa hasilnya?
Kami mendapatkan 
manajemen negara penuh dengan banyak keuntungan:
- sumber data tunggal untuk aplikasi,
- negara disimpan secara terpisah dari aplikasi,
- gaya penulisan tunggal untuk semua pengembang dalam proyek,
- 
changeDetectionStrategy.OnPush di semua komponen aplikasi,
- debugging yang nyaman melalui 
redux-devtools ,
- kemudahan pengujian, seperti 
reduksi adalah fungsi murni.
Namun ada juga kelemahannya:- Sejumlah besar modul yang tampaknya tidak dapat dipahami,
- banyak jenis kode yang sama yang tidak akan Anda lihat tanpa kesedihan
- Kesulitan dalam menguasai karena semua hal di atas.
CRUD
Sebagai aturan, sebagian besar aplikasi ditempati oleh pekerjaan dengan objek (pembuatan, membaca, memperbarui, menghapus), oleh karena itu, untuk kenyamanan pekerjaan, konsep 
CRUD telah dibuat (Buat, Baca, Perbarui, Hapus). Dengan demikian, operasi dasar untuk bekerja dengan semua jenis objek distandarisasi. Telah booming untuk waktu yang lama di backend. Banyak perpustakaan membantu menerapkan fungsi ini dan menyingkirkan pekerjaan rutin.
Dalam 
NgRx , modul 
entitas bertanggung jawab untuk 
CRUD , dan jika Anda melihat contoh implementasinya, Anda dapat segera melihat bahwa ini adalah bagian terbesar dan paling kompleks dari 
NgRx . Itulah sebabnya 
John Papa dan 
Ward Bell membuat data 
angular-ngrx .
angular-ngrx-data
angular-ngrx-data adalah 
pustaka add-in 
NgRx yang memungkinkan Anda untuk bekerja dengan array data tanpa menulis kode tambahan.
Selain menciptakan 
manajemen negara yang lengkap, ia melakukan pembuatan layanan dengan 
http untuk berinteraksi dengan server.
Pertimbangkan sebuah contoh
Instalasi npm install --save @ngrx/store @ngrx/effects @ngrx/entity @ngrx/store-devtools ngrx-data 
Modul data sudut-ngrx import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { EntityMetadataMap, NgrxDataModule, DefaultDataServiceConfig } from 'ngrx-data'; const defaultDataServiceConfig: DefaultDataServiceConfig = { root: 'crud' }; export const entityMetadata: EntityMetadataMap = { Hero: {}, User:{} }; export const pluralNames = { Hero: 'heroes' }; @NgModule({ imports: [ CommonModule, NgrxDataModule.forRoot({ entityMetadata, pluralNames }) ], declarations: [], providers: [ { provide: DefaultDataServiceConfig, useValue: defaultDataServiceConfig } ] }) export class EntityStoreModule {} 
Hubungkan ke aplikasi @NgModule({ imports: [ BrowserModule, HttpClientModule, StoreModule.forRoot({}), EffectsModule.forRoot([]), EntityStoreModule, StoreDevtoolsModule.instrument({ maxAge: 25, }), ], declarations: [ AppComponent ], providers: [], bootstrap: [AppComponent] }) export class AppModule {} 
Kami baru saja mendapatkan API yang dihasilkan untuk bekerja dengan back-end dan integrasi API dengan NgRx , tanpa menulis efek tunggal, peredam dan tindakan serta pemilih.Mari kita teliti lebih detail apa yang terjadi di sini.
Konstanta defaultDataServiceConfig menetapkan konfigurasi untuk API kami dan menghubungkan ke modul 
penyedia . Properti 
root menunjukkan ke mana harus mencari permintaan. Jika tidak disetel, maka defaultnya adalah "api".
 const defaultDataServiceConfig: DefaultDataServiceConfig = { root: 'crud' }; 
Konstanta entityMetadata mendefinisikan nama-nama 
toko yang akan dibuat ketika 
NgrxDataModule.forRoot terhubung.
 export const entityMetadata: EntityMetadataMap = { Hero: {}, User:{} }; ... NgrxDataModule.forRoot({ entityMetadata, pluralNames }) 
Jalur ke API terdiri dari jalur dasar (dalam kasus kami "kasar") dan nama toko.
Misalnya, untuk mendapatkan pengguna dengan nomor tertentu, pathnya adalah “crud / user / {userId}”.
Untuk mendapatkan daftar pengguna yang lengkap, huruf "s" - "crud / user 
s " ditambahkan ke akhir nama toko secara default.
Jika Anda memerlukan rute yang berbeda untuk mendapatkan daftar lengkap (misalnya, "pahlawan" dan bukan "pahlawan"), Anda dapat mengubahnya dengan mengatur 
nama jamak dan menghubungkannya ke 
NgrxDataModule.forRoot .
 export const pluralNames = { Hero: 'heroes' }; ... NgrxDataModule.forRoot({ entityMetadata, pluralNames }) 
Koneksi dalam komponen
Untuk menghubungkan dalam komponen, Anda harus meneruskan konstruktor 
entitasServices ke konstruktor dan menggunakan metode 
getEntityCollectionService untuk memilih layanan penyimpanan yang diinginkan
 import { Component, OnInit, ChangeDetectionStrategy } from '@angular/core'; import { Observable } from 'rxjs'; import { Hero } from '@appModels/hero'; import { EntityServices, EntityCollectionService } from 'ngrx-data'; @Component({ selector: 'app-heroes', templateUrl: './heroes.component.html', styleUrls: ['./heroes.component.css'], changeDetection: ChangeDetectionStrategy.OnPush }) export class HeroesComponent implements OnInit { heroes$: Observable<Hero[]>; heroesService: EntityCollectionService<Hero>; constructor(entityServices: EntityServices) { this.heroesService = entityServices.getEntityCollectionService('Hero'); } ... } 
Untuk mengikat daftar ke komponen, cukup untuk mengambil 
entitas $ properti dari layanan, dan untuk mendapatkan data dari server, panggil metode 
getAll () .
 ngOnInit() { this.heroes$ = this.heroesService.entities$; this.heroesService.getAll(); } 
Selain data dasar, Anda juga bisa mendapatkan:
- 
dimuat $ , 
memuat $ - mendapatkan status memuat data,
- 
kesalahan $ - kesalahan saat layanan berjalan,
- 
hitung $ - jumlah total catatan dalam repositori.
Metode utama berinteraksi dengan server:
- 
getAll () - mendapatkan seluruh daftar data,
- 
getWithQuery (kueri) - mendapatkan daftar yang difilter menggunakan parameter kueri,
- 
getByKey (id) - mendapatkan satu catatan dengan pengidentifikasi,
- 
add (entitas) - menambahkan entitas baru dengan permintaan dukungan,
- 
delete (entitas) - menghapus entitas dengan permintaan dukungan,
- 
perbarui (entitas) - perbarui entitas dengan permintaan dukungan.
Metode penyimpanan lokal:
- 
addManyToCache (entitas) - menambahkan array entitas baru ke repositori,
- 
addOneToCache (entitas) - menambahkan entitas baru hanya ke repositori,
- 
removeOneFromCache (id) - hapus satu entitas dari repositori,
- 
updateOneInCache (entitas) - perbarui entitas dalam repositori,
- 
upsertOneInCache (entitas) - jika entitas dengan id yang ditentukan ada, itu diperbarui, jika tidak, yang baru dibuat,
- dan lainnya
Contoh penggunaan komponen import { EntityCollectionService, EntityServices } from 'ngrx-data'; import { Hero } from '../../core'; @Component({ selector: 'app-heroes', templateUrl: './heroes.component.html', changeDetection: ChangeDetectionStrategy.OnPush }) export class HeroesComponent implements OnInit { heroes$: Observable<Hero[]>; heroesService: EntityCollectionService<Hero>; constructor(entityServices: EntityServices) { this.heroesService = entityServices.getEntityCollectionService('Hero'); } ngOnInit() { this.heroes$ = this.heroesService.entities$; this.getHeroes(); } getHeroes() { this.heroesService.getAll(); } addHero(hero: Hero) { this.heroesService.add(hero); } deleteHero(hero: Hero) { this.heroesService.delete(hero.id); } updateHero(hero: Hero) { this.heroesService.update(hero); } } 
Semua metode 
angular-ngrx-data dibagi menjadi bekerja secara lokal dan berinteraksi dengan server. Ini memungkinkan Anda untuk menggunakan pustaka saat memanipulasi data pada klien dan menggunakan server.
Penebangan
Untuk masuk, Anda perlu menyuntikkan 
EntityServices ke dalam komponen atau layanan dan menggunakan properti:
--ucedActions $ - untuk tindakan logging,
- 
entityActionErrors $ - untuk kesalahan logging.
 import { Component, OnInit } from '@angular/core'; import { MessageService } from '@appServices/message.service'; import { EntityServices } from 'ngrx-data'; @Component({ selector: 'app-messages', templateUrl: './messages.component.html', styleUrls: ['./messages.component.css'] }) export class MessagesComponent implements OnInit { constructor( public messageService: MessageService, private entityServices: EntityServices ) {} ngOnInit() { this.entityServices.reducedActions$.subscribe(res => { if (res && res.type) { this.messageService.add(res.type); } }); } } 
Pindah ke repositori NgRx utama
Seperti yang diumumkan pada 
ng-conf 2018 , 
angular-ngrx-data akan segera dimigrasikan ke repositori 
NgRx utama.
Mengurangi Boilerplate dengan video pembicaraan NgRx - Brandon Roberts & Mike Ryan
 Referensi
Pembuat anguar-ngrx-data:- John Papa 
twitter.com/John_Papa- Ward Bell 
twitter.com/wardbellRepositori resmi:- 
NgRx- 
angular-ngrx-dataContoh aplikasi:- 
dengan NgRx tanpa angular-ngrx-data- 
dengan NgRx dan data angular-ngrxKomunitas Angular berbahasa Rusia di Telegram