如何使用 ngrx/entity(EntityState 和 EntityAdapter)规范化深度嵌套的数据

mar*_*ran 5 normalization state-management ngrx ngrx-entity angular8

我希望从我的服务器规范化数据,以便我可以更轻松地将它与 ngrx/entity 一起使用。

我喜欢 ngrx/entity 如何通过提供 EntityState 接口和 EntityAdapter 来降低减速器和其他东西的复杂性。但是我发现它不适用于嵌套数据。

我有 3 个级别的数据:

训练 -> 练习 -> 套

如果我将它与 ngrx/entity 的经典模式一起使用,当我使用嵌套数据时,它会很快变得拥挤。

下面是我在使用 ngrx/entity 时遇到的第一件事 当我将所有训练数据放入时,这就是数据标准化的方式 在那之后,我四处窥探并进入了 normalizr 库 输出 我喜欢 normalizr 如何规范化我的数据,并且还用仅 id 作为其他实体(练习、集合)的键替换嵌套数组值

我首先尝试的是组合多个实体状态,如下所示: 实体状态 但这需要改变我的服务器以及大量的逻辑和努力。

我想要的是以某种方式将 normalizr 与 ngrx/entity 结合起来。 得到 normalizr 给我的同样的东西,但可以自由地使用来自 ngrx/entity 的实体适配器 api 它是选择器和其他代码,这些代码在我的服务中来自 ngrx/entity

所以最重要的是,我的问题是如何在没有某种服务器工作的情况下使用 ngrx/entity(就像 normalizr 库那样)规范化深层嵌套数据。

mar*_*ran 6

所以我在仍然使用 NGRX 的同时找到了一些解决方案

在开始之前我只想说 ngrx 也有 ngrx/data pack,它提供了更少的样板文件。但当我读到它时,我找到了我的问题的明确答案:

https://ngrx.io/guide/data/limitations “该库浅克隆集合中的实体数据。它不会克隆复杂、嵌套或数组属性。您必须进行深度相等测试,并且在要求 NgRx Data 保存数据之前克隆自己。”

我相信对于 ngrx/entity 来说也是如此。

我开始寻找替代解决方案:BreezeJs、NGXS、Akita,从中我只发现 NGXS 对我来说可以快速理解,但需要努力将我的 ngrx 实现与项目分离。

所以我回到 ngrx 并尝试为 3 层深度嵌套数据做一个解决方法

创建 3 个独立的实体状态(我将尝试使用 ngrx/data ,它肯定可以减少所有样板文件)

创建一个函数,该函数将返回所有必需的实体和每个实体的 id(使用 normalizr 进行标准化)

export function normalizeTrainingArray(trainings: Training[]) {
var normalized = normalize(trainings, trainingsSchema);

var entities = {
    trainings: {},
    exercises: {},
    sets: {}
}
entities.trainings = normalized.entities.trainings ? normalized.entities.trainings : {};
entities.exercises = normalized.entities.exercises ? normalized.entities.exercises : {};
entities.sets = normalized.entities.sets ? normalized.entities.sets : {};

var ids = {
    trainingIds: [],
    exerciseIds: [],
    setIds: []
}
ids.trainingIds = normalized.entities.trainings ? Object.values(normalized.entities.trainings).map(x => x.id) : [];
ids.exerciseIds = normalized.entities.exercises ? Object.values(normalized.entities.exercises).map(x => x.id) : [];
ids.setIds = normalized.entities.sets ? Object.values(normalized.entities.sets).map(x => x.id) : [];

return {
    entities,
    ids
}
Run Code Online (Sandbox Code Playgroud)

像这样的东西就足够了。发送normalizeData操作并使用effect来调用此方法并为fetchedData分派3个不同的操作...

大致如下:

   trainingsNormalized$ = createEffect(() =>
    this.actions$.pipe(
        ofType(TrainingActions.normalizeTrainings),
        tap(payload => {

            var normalized = normalizeTrainingArray(payload.trainings);

            this.store.dispatch(TrainingActions.trainingsFetched({ entities: normalized.entities.trainings, ids: normalized.ids.trainingIds }))
            this.store.dispatch(ExerciseActions.exercisesFetched({ entities: normalized.entities.exercises, ids: normalized.ids.exerciseIds }))
            this.store.dispatch(SetActions.setsFetched({ entities: normalized.entities.sets, ids: normalized.ids.setIds }))
        })
    )
    , { dispatch: false });
Run Code Online (Sandbox Code Playgroud)

在一个示例减速器中:

    // GET ALL
on(TrainingActions.trainingsFetched, (state: TrainingState, payload: { entities: Dictionary<Training>, ids: string[] }) => {
    return {
        ...state,
        entities: payload.entities,
        ids: payload.ids
    }
}),
Run Code Online (Sandbox Code Playgroud)

结果是:

结果