如何在@ngrx/data 中扩展自定义实体集合 Reducer
在阅读并尝试了到目前为止将我的 additionalCollectionStates 更新为我的 EntityCollection 的所有方法后(基于文档中的一些信息, 这个 SO 问题 、 这个其他 SO 问题 、 另一个 SO 问题 和 来自 github 存储库的问题 )我发现自己被困在一个应该是一个相当常见的任务上,并且在使用 ngrx 之前根本没有问题:使用 ngrx 对存储的实体集合进行排序。搜索解决方案让我回到了已经看过 5 次的相同 10 个链接,并且大多数示例要么太基础要么太过时,要么根本不使用 ngrx/data,或者类似的问题根本没有答案。
环境 :Angular 9.1.7、ngrx 9.1.2、macOS 10.14.6
目标
按照
在文档中
所述,将自己的
additionalCollectionState
添加并更新到我的 EntityCollection,以进行排序和分页。我
没有
从服务器获取
sortQuery
属性(sortField、sortDirection),而是在我的 GUI 中设置它并根据它们对集合进行排序。我知道 EntityMetadataMap 上存在
sortComparer
设置,但它对我来说不够灵活,我想动态地对应用程序中不同 MatTable 中的集合进行排序。每当 MatTable 中的排序顺序/字段发生变化时,都应该更新此附加属性。
我目前所取得的成就:
如上所述的 additionalCollectionState 的 ngrx/data 方式 有效 ,我的集合使用我在 EntityMetadataMap 中提供的数据 初始化 。但是,更新自定义集合属性并不像我从文档中理解的那样有效。由于我没有从服务器获取 sortQuery 属性,因此不会调用 ResultsHandler。
我通过两种方式解决了这个问题:a) 使用 ngrx 和 ngrx/entity 方法和 b) 通过尝试扩展自定义 EntityCollectionReducer 来使用 ngrx/data 方式
a) 我成功创建了自定义 Action 和自定义 Reducer 并更新了 state 属性 ,但没有更新之前定义的 collection 属性 。我的操作基本上是:
export const selectResourceSortQuery = createAction('[Resource] Get Last Sort Query', props<{ sortQuery: any }>());
它是从具有以下内容的服务调用的:
this.store.dispatch(selectResourceSortQuery({ sortQuery }));
并在 Reducer 中处理:
import { EntityState, EntityAdapter, createEntityAdapter } from '@ngrx/entity';
import { Action, createReducer, on } from '@ngrx/store';
import * as ResourceActions from './resource.actions';
import { Resource } from 'src/app/sdk';
export interface State extends EntityState<Resource> {
// additional state property
sortQuery: any | null;
}
export const adapter: EntityAdapter<Resource> = createEntityAdapter<Resource>();
export const initialState: State = adapter.getInitialState({
// additional entity state properties
sortQuery: {
sortDirection: 'asc',
sortField: 'product'
},
});
export const resourceReducer = createReducer(
initialState,
on(ResourceActions.selectResourceSortQuery, (state, { sortQuery }) => {
console.log(`REDUCER called`);
return { ...state, sortQuery };
}),
);
export function reducer(state: State | undefined, action: Action) {
return resourceReducer(state, action);
}
Reducer 在 Reducers/index.ts 中导出:
import {
ActionReducer,
ActionReducerMap,
createFeatureSelector,
createSelector,
MetaReducer
} from '@ngrx/store';
import { environment } from 'src/environments/environment';
import * as fromResourceReducers from 'src/app/store/entities/resource/resource.reducer';
export interface State {
}
export const reducers: ActionReducerMap<State> = {
resourceSort: fromResourceReducers.reducer
};
export const metaReducers: MetaReducer<State>[] = !environment.production ? [] : [];
在 app.module 中,设置如下:
StoreModule.forRoot(reducers, {
metaReducers: [
ngrxEntityRelationshipReducer //ignore this
],
runtimeChecks: {
strictStateImmutability: true,
strictActionImmutability: true
}
}),
这将创建一个
resourceSort
集合,其具有
sortQuery
属性,如下图所示:
,并根据更改后的 sortQuery 属性对其进行更新。但是,我更喜欢使用 Resource entityCache 集合中已初始化的 sortQuery 属性(是的,我也尝试过使用
StoreModule.forFeature('entityCache', Reducers)
将其添加到
entityCache
功能中,忽略它):
b) 因此,另一种方法是在 app.module 中使用 EntityCollectionReducerRegistry:
import { resourceReducer } from './store/entities/resource/resource.reducer';
// does not work: resourceReducer is of type ActionReducer<State, Action> and the same as posted above (with the added handling of the sortQuery property), and apparently what this function expects
entityCollectionReducerRegistry.registerReducer('Resources', resourceReducer);
// this does work, but just creates the basic default entity collection reducer without my custom sortQuery handling
entityCollectionReducerRegistry.registerReducer('Resources', entityCollectionReducerFactory.create('resources'))
来自文档:
The Entity Reducer is the master reducer for all entity collections in the stored entity cache. The library doesn't have a named entity reducer type. Rather it relies on the EntityCacheReducerFactory.create() method to produce that reducer, which is an NgRx ActionReducer<EntityCache, EntityAction>.
我有一种感觉,使用 b) 方法我可能走在正确的轨道上,但是我却缺乏文档和示例如何处理这个问题 - 有人能帮助我吗?
如果需要更多详细信息,我很乐意提供,如果由于英语不是我的母语而导致某些内容不清楚,我很乐意澄清,如有必要,请尝试提供一个简化的 stackblitz 示例。谢谢!
您处理这个问题的方式与我最初处理的方式相同:尝试注册自定义 Reducer 来处理 additionalCollectionState 属性。花了一些时间解读文档后,我终于能够更新集合属性。以下是文档中的内容以及我如何做到的:
The NgRx Data library generates selectors for these properties, but has no way to update them. You'll have to create or extend the existing reducers to do that yourself. ( additionalCollectionState )
此后,它继续使用后端示例,但我不需要它;但是,我使用了第 2 步中的信息来扩展(读作 hack)EntityCollectionReducerMethods 来拦截我的属性:
extended-entity-collection-reducer-methods.ts
export class ExtendedEntityCollectionReducerMethods<T> extends EntityCollectionReducerMethods<T> {
constructor(
public entityName: string,
public definition: EntityDefinition<T>) {
super(entityName, definition);
}
protected updateOne(collection: EntityCollection<T>, action: EntityAction<Update<T>>): EntityCollection<T> {
const superMethod = {...super.updateOne(collection, action)};
if (action.payload.entityName === 'Project' &&
(action.payload.data.changes as any).id === 0) {
(superMethod as any).lastProjectId = (action.payload.data.changes as any).lastProjectId;
}
return superMethod;
}
然后需要由 Factory 实例化:
extended-entity-collection-reducer-methods-factory.ts
@Injectable()
export class ExtendedEntityCollectionReducerMethodsFactory {
constructor(
private entityDefinitionService: EntityDefinitionService
) {
}
create<T>(entityName: string): EntityCollectionReducerMethodMap<T> {
const definition = this.entityDefinitionService.getDefinition<T>(entityName);
const methodsClass = new ExtendedEntityCollectionReducerMethods(entityName, definition);
return methodsClass.methods;
}
}
ExtendedEntityCollectionReducerMethodsFactory 需要替换原始的那个。
app-module.ts
providers: [
{
provide: EntityCollectionReducerMethodsFactory,
useClass: ExtendedEntityCollectionReducerMethodsFactory
}
]
现在,您可以在组件中偷偷摸摸地执行此操作:
this.someService.updateOneInCache({id: 0, lastProjectId: projectId} as any);
someService 需要扩展了不起的 EntityCollectionServiceBase 类:
some-service.ts
@Injectable({providedIn: 'root'})
export class SomeService extends EntityCollectionServiceBase<SomeEntity> {
constructor(serviceElementsFactory: EntityCollectionServiceElementsFactory) {
super('SomeEntity', serviceElementsFactory);
}
}
ExtendedEntityCollectionReducerMethods 中的 updateOne 方法将通过操作的有效负载拾取属性,您可以在返回 superMethod 的结果之前在那里设置它。
我觉得这有点令人不寒而栗,但它确实有效。在尝试使用 EntityCacheReducerFactory 及其奇怪的伙伴后,我找不到其他方法来更新集合的附加属性。
我已将更新方法的 id 设置为 0,否则它会抱怨 id 缺失。由于我没有任何 id 为 0 的实体,因此它应该可以正常工作而不会产生任何意外后果。