I am currently trying to convert my ngrx store to use ngrx/data to handle my entities. One of the trickier obstacles I have hit is handling API endpoints that return data for multiple entities. A simple example- lets say I have the following models that can be retrieved from my API:
export interface Parent {
id: string;
name: string;
}
export interface Child {
id: string;
name: string;
parent: string
}
And I have the following endpoints:
/api/parents #GET (list of parents), POST (create new parent)
/api/parents/<PARENT_ID> #GET, PATCH, PUSH, DELETE (a single parent)
/api/children #GET (list of children), POST (create new child)
/api/children/<CHILD_ID> #GET, PATCH, PUSH, DELETE (a single child)
/api/families #GET (all parents and children)
/api/families
is a convenience function, that returns all parents and children in the format:
{
parents: Parent[];
children: Child[];
}
In the past, I have made a separate families
entry for my ngrx store with just loading/loaded parameters, then a set of LoadFamilies
actions to grab the data from the server. I then include those actions in my Parents and Children store reducers, and act on them appropriately. What I want to do now is add this additional reducer to my existing ngrx/data controlled entities. Any thoughts/example code on how to go about this?
The objective of this article is to provide a technical implementation of the NGRX for an application with a complexity that could benefit from adding feature store(s) in addition to the root store.
Reducers in NgRx are responsible for handling transitions from one state to the next state in your application. Reducer functions handle these transitions by determining which actions to handle based on the type.
Reducers are pure functions accepting two arguments, the previous state and an Action. When an Action is dispatched ngrx goes through all the reducers passing as arguments the previous state and the Action, in the order that the reducers where created, until it finds a case for that action.
NgRx Data is an extension of NgRx state management system for angular framework. The advantage of using NgRx Data is simplicity and a drastic code reduction in compare to traditional approach. Of course the world isn't perfect and you can't use this extension everywhere.
Extend DefaultDataService
see https://ngrx.io/guide/data/entity-dataservice#custom-entitydataservice
Within a pipe map nested data to entities (I use normalizr)
Then dispatch entity cache action(s) using EntityActionFactory as eaFactory
to update store as you see fit
example with Parent <--> Child of ConcreteRecordHeader <--> ConcreteRecordLoad
getById(key: string | number): Observable<ConcreteRecordHeader> {
return super.getById(key).pipe(
map((denorm: any) =>
this.categoryCacheService.normalizeCategory([denorm])
),
tap((norm: NormalizedCategories) => this.mergeQuerySet(norm)),
map((norm: NormalizedCategories) => this.pickEntities(norm)[0])
) as Observable<ConcreteRecordHeader>;
}
protected mergeQuerySet(norm: NormalizedCategories) {
console.log("⚠️ Merging server data ⚠️");
const options: EntityActionOptions = {
mergeStrategy: MergeStrategy.PreserveChanges
};
const data = norm.entities.concreteRecordLoads
? Object.values(norm.entities.concreteRecordLoads)
: [];
const action = this.eaFactory.create(
"ConcreteRecordLoad",
EntityOp.SAVE_UPSERT_MANY_SUCCESS,
data,
options
);
this.entityCacheDispatcher.dispatch(action);
}
protected pickEntities(norm: NormalizedCategories) {
return (norm.entities.concreteRecordHeaders
? Object.values(norm.entities.concreteRecordHeaders)
: []) as ConcreteRecordHeader[];
}
Sadly this.entityCacheDispatcher.mergeQuerySet()
isn't working with mergeStrategy which is the natural API to use. I plan on submitting a PR for this.
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