2

I have an EntityState, let's say of type Model: EntityState<Model>.

I have an API that is returning a list of partial models. Partial<Model>[].

I want to upsert this list. For entities that are already present in the store / entity state (matched by id of course), I want to update all of the present properties.

For entities that are not present, I would like to null out missing fields and add these entities.

What is the best way to accomplish this? The documentation on how exactly the adapter methods work is pretty sparse (https://ngrx.io/guide/entity/adapter#adapter-collection-methods).

upsert requires Model[], does not accept Partial<Model>[], although the documentation implies that it supports partial updates somehow.

The add methods do not specify what they do for existing id matches... presumably they ignore the new items and do not add / replace?

The update methods do not indicate what happens if the listed ID is not present.

I'm thinking my best bet might be to call addMany and then updateMany in order to get the behavior I want, but seems like upsert is intended for this purpose. The challenge is probably mapping these Partial<Model> types into Model for the instances that are missing in the entity state... i.e. nulling out missing fields for new entries.

Thoughts / advice? Thanks!

Josh G
  • 14,068
  • 7
  • 62
  • 74

2 Answers2

2

This is my solution for the time being, unless I hear of a better option from someone who knows ngrx entities better.

interface IModel {
  id: string;
}

export function upsertMany<TApiType extends IModel, TStoreModel>(
  adapter: EntityAdapter<TStoreModel>,
  models: TApiType[],
  addMap: ((model: TApiType) => TStoreModel),
  updateMap: ((model: TApiType) => Partial<TStoreModel>),
  state: EntityState<TStoreModel>,
): EntityState<TStoreModel> {
  state = adapter.updateMany(models.map(model => ({
    id: model.id,
    changes: updateMap(model),
  })), state);
  return adapter.addMany(models.map(addMap), state);
}
Josh G
  • 14,068
  • 7
  • 62
  • 74
  • This solution is probably twice as expensive as normal because we are mapping all of the api entities twice and attempting to add each and then update each independently. – Josh G Jan 02 '21 at 18:43
0

What about this one:

on(MyActions.partialUpdate, (state, action) => {
  return {
    ...state,
    courses: adapter.upsertMany(action.partialCourses.map(c => {

      let course = state.courses.entities[c.id] !== undefined ?
        { ...state.courses.entities[c.id] } :
        new Course();

      course.someProperty = c.someProperty;

      return course;
    }), state.courses)
  }
})
rwasik
  • 296
  • 4
  • 17