I wish to normalize data from my server so I can use it more easily with ngrx/entity.
I like how ngrx/entity reduces complexity of reducers and other stuff by providing EntityState interface and EntityAdapter. However I don't find it to work good with nested data.
I have 3 levels of data:
Training -> exercises -> sets
If I use this with classic pattern of ngrx/entity it gets crowded fast when I work with nested data.
Below is first thing I ran onto when using ngrx/entity After that I snooped around and got to normalizr library I like how normalizr normalizes my data and also replaces nested array values with only id as keys to other entities (exercises, sets)
What I tried first was combine multiple entity states like so: But this requires changing up my server and a lot of logic and effort.
What I'd like is to somehow combine normalizr with ngrx/entity.. Get the same thing normalizr gives me but have the freedom to use entity adapter api from ngrx/entity it's selectors and other code that's at my service from ngrx/entity
So bottom line my question would be how to normalize deep nested data with ngrx/entity (like normalizr library does) without some kind of server effort.
So I found some workaround solution while still using NGRX
Before I start I just want to say that ngrx also has ngrx/data pack which provides less boilerplate. But while I was reading about it I found a definitive answer to my question:
https://ngrx.io/guide/data/limitations "This library shallow-clones the entity data in the collections. It doesn't clone complex, nested, or array properties. You'll have to do the deep equality tests and cloning yourself before asking NgRx Data to save data."
I believe this is also true for ngrx/entity.
I started to look for alternative solutions: BreezeJs, NGXS, Akita from which I only found NGXS understandable to me fast but would require effort to detach my ngrx implementation from project.
So I got back to ngrx and tried to do a workaround for 3 levels deep nested data
Create 3 separate entity states ( I'll try to use ngrx/data that could certanly reduce all the boilerplate)
Create a function that will return all necessary entities and ids for each entity (use normalizr for normalization)
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
}
Something like this will suffice. Send normalizeData action and use effect to call this method and dispatch 3 different actions for fetchedData...
Something along the lines of:
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 });
And in one sample reducer:
// GET ALL
on(TrainingActions.trainingsFetched, (state: TrainingState, payload: { entities: Dictionary<Training>, ids: string[] }) => {
return {
...state,
entities: payload.entities,
ids: payload.ids
}
}),
Result is:
@ngrx/entity
does not provide ways to normalize data. You can use normalizr in combination with @ngrx/entity
, you could for example normalize your data in the Angular service/NgRx Effect/NgRx reducer.
The Redux docs has some pages about using normalizer with redux at https://redux.js.org/recipes/structuring-reducers/normalizing-state-shape.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With