6

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?

MarkD
  • 4,864
  • 5
  • 36
  • 67
  • Any luck with that? – Ssss Nov 15 '19 at 11:45
  • 1
    Unfortunately, not using ngrx/data. I did find another similar library, [auto-entity](https://briebug.gitbook.io/ngrx-auto-entity/), which handles simple stuff in a similar way to ngrx/data, but offers a lot more flexibility (at least documented) for doing more complex things like what I was trying to do in my question. – MarkD Nov 15 '19 at 18:06

1 Answers1

2

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.

Andrew Allen
  • 6,512
  • 5
  • 30
  • 73