
迄今为止,没有
状态管理还无法完成一个大型
SPA应用程序。 在这一领域,
Angular有多种解决方案。 其中最流行的是
NgRx 。 它使用
RxJs库实现
Redux模式,并具有良好的工具。
在本文中,我们将简要介绍主要的
NgRx模块,并更详细地介绍
angular-ngrx数据库,该库使您可以在五分钟内完成
状态管理的完整
CRUD 。
NgRx评论
您可以在以下文章中阅读有关
NgRx的更多信息:
-Angular / NGRX上的反应性应用程序。 第1部分。简介-Angular / NGRX上的反应性应用程序。 第2部分。存储-Angular / NGRX上的反应性应用程序。 第3部分。效果简要考虑
NgRx的主要模块及其优缺点。
NgRx / store-实现Redux模式。
简单的商店实施反作用
export const INCREMENT = 'INCREMENT'; export const DECREMENT = 'DECREMENT'; export const RESET = 'RESET';
减量器
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; } }
。
连接到模块
import { NgModule } from '@angular/core'; import { StoreModule } from '@ngrx/store'; import { counterReducer } from './counter'; @NgModule({ imports: [StoreModule.forRoot({ count: counterReducer })], }) export class AppModule {}
在组件中使用
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-允许您通过
redux-devtools跟踪应用程序中的更改。
连接例 import { StoreDevtoolsModule } from '@ngrx/store-devtools'; @NgModule({ imports: [ StoreModule.forRoot(reducers), // StoreModule StoreDevtoolsModule.instrument({ maxAge: 25, // 25 }), ], }) export class AppModule {}
NgRx /效果 -允许您将进入应用程序的数据添加到存储库,例如http请求。
例子./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) {} }
将效果连接到模块
import { EffectsModule } from '@ngrx/effects'; import { AuthEffects } from './effects/auth.effects'; @NgModule({ imports: [EffectsModule.forRoot([AuthEffects])], }) export class AppModule {}
NgRx /实体 -提供使用数据数组的功能。
例子user.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;
减速器/ 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] );
结果如何?
我们获得全面的
状态管理,并具有许多优势:
-应用程序的单个数据源,
-状态与应用程序分开存储,
-项目中所有开发人员的单一写作风格,
-应用程序所有组件中的
changeDetectionStrategy.OnPush ,
-通过
redux-devtools进行方便的调试,
-易于测试
减速器是纯函数。
但是也有缺点:-大量看似难以理解的模块,
-很多相同类型的代码,如果您不感到悲伤就不会看,
-由于上述所有原因,难以掌握。
欺诈
通常,应用程序的重要部分是处理对象(创建,读取,更新,删除),因此,为了方便工作,创建了
CRUD概念(创建,读取,更新,删除)。 因此,用于处理所有类型对象的基本操作已标准化。 后端一直在蓬勃发展。 许多库有助于实现此功能并摆脱日常工作。
在
NgRx中 ,
实体模块负责
CRUD ,如果查看其实现示例,您会立即看到这是
NgRx最大,最复杂的部分。 这就是
John Papa和
Ward Bell创建
angular-ngrx-data的原因 。
角度ngrx数据
angular-ngrx-data是
NgRx加载项库,使您无需编写额外的代码即可使用数据数组。
除了创建完整的
状态管理之外 ,她还负责使用
http创建服务以与服务器交互。
考虑一个例子
安装方式 npm install --save @ngrx/store @ngrx/effects @ngrx/entity @ngrx/store-devtools ngrx-data
Angular-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 {}
连接到应用程序 @NgModule({ imports: [ BrowserModule, HttpClientModule, StoreModule.forRoot({}), EffectsModule.forRoot([]), EntityStoreModule, StoreDevtoolsModule.instrument({ maxAge: 25, }), ], declarations: [ AppComponent ], providers: [], bootstrap: [AppComponent] }) export class AppModule {}
我们仅获得了生成的用于后端的API以及该API与NgRx的集成, 而无需编写单个效果,reduce和动作以及选择器。让我们更详细地检查这里发生了什么。
defaultDataServiceConfig常量为我们的API设置配置并连接到
providers模块。
根属性指示去哪里请求。 如果未设置,则默认值为“ api”。
const defaultDataServiceConfig: DefaultDataServiceConfig = { root: 'crud' };
EntityMetadata常量定义在连接
NgrxDataModule.forRoot时将创建的
存储的名称。
export const entityMetadata: EntityMetadataMap = { Hero: {}, User:{} }; ... NgrxDataModule.forRoot({ entityMetadata, pluralNames })
API的路径由基本路径(在我们的示例中为“ crud”)和商店名称组成。
例如,要获得具有特定编号的用户,路径应为“ crud / user / {userId}”。
为了获得完整的用户列表,默认情况下,在商店名称的末尾添加字母“ s”-“ crud / user
s ”。
如果您需要其他途径来获取完整列表(例如,“ heroes”而不是“ heros”),则可以通过设置
pluralNames并将它们连接到
NgrxDataModule.forRoot来进行
更改 。
export const pluralNames = { Hero: 'heroes' }; ... NgrxDataModule.forRoot({ entityMetadata, pluralNames })
组件中的连接
要连接组件,您需要将
entityServices构造函数传递给该构造函数,并使用
getEntityCollectionService方法选择所需存储的服务。
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'); } ... }
要将列表绑定到组件,只需从服务中获取
实体$属性,并从服务器获取数据,只需调用
getAll()方法即可。
ngOnInit() { this.heroes$ = this.heroesService.entities$; this.heroesService.getAll(); }
此外,除了基本数据之外,您还可以获得:
-
加载$ ,
加载$ -获取加载数据的状态,
-
错误$ -服务运行时发生错误,
-count $ -存储库中的记录总数。
与服务器交互的主要方法:
-getAll() -获取整个数据列表,
-getWithQuery(查询) -使用查询参数过滤列表,
-getByKey(id) -按标识符获取一条记录,
-
添加(实体) -添加带有支持请求的新实体,
-
删除(实体) -删除有支持请求的实体,
-
更新(实体) -用支持请求更新实体。
本地存储方式:
-addManyToCache(实体) -向存储库添加新实体数组,
-addOneToCache(实体) -仅向商店添加新实体,
-removeOneFromCache(id) -从存储库中删除一个实体,
-updateOneInCache(实体) -更新存储库中的实体,
-upsertOneInCache(实体) -如果存在具有指定ID的实体,则将其更新;否则,将创建一个新实体,
-和其他
组件用法示例 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); } }
所有
angular-ngrx-data方法都分为本地工作和与服务器交互。 这使您可以在客户端和服务器上使用数据时使用库。
记录中
为了进行日志记录,您需要将
EntityServices注入组件或服务并使用属性:
-reduceActions $ -用于记录操作,
-entityActionErrors $ -用于记录错误。
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); } }); } }
移至主要的NgRx存储库
正如在
ng-conf 2018上宣布的那样,
angular-ngrx-data将
很快迁移到主要的
NgRx存储库中。
用NgRx谈话视频减少样板-布兰登·罗伯茨和迈克·瑞安
参考文献
anguar-ngrx-data的创建者:-约翰·帕帕
twitter.com/John_Papa-沃德·贝尔
twitter.com/wardbell官方资料库:-NgRx-angular-ngrx-data应用实例:-
使用NgRx而不使用angular-ngrx-data-
使用NgRx和angular-ngrx-data俄语的电报社区