Wednesday, July 03, 2019

Ngrx Entities and One to Many Relationships

When I started with Ngrx the Entity module didn't exist. My state consisted of arrays of objects. The reducers and selectors were array manipulations. It worked well but if the state had a large number of objects, the filter and maps were expensive and required lots of code.
The Entity library made it much simpler. My reducers are much less code and dramatically simple in comparison. The selectors are about half the length. It all works quickly, is easy to set up.

Essentially the data is stored as an entity object. { [id: string|number]: dataobj} There is a list of id's, which can be sorted. You access a specific object using the id as a property. entity[id]. If you have a list of id's, idlist.map(i => entity[i] will give you a list of objects. The Entity can sort the ids, extract whatever key you want from your data. But what if you have a relational data structure?

For example, you have a contact list.
interface Contact {
  id: string;
  name: string;
  note: string;
}

You also have an email address list. Each contact may have one or many email addresses.
interface Email {
  id: string;
  email: string;
  note: string;
  contactid: string;
}

For each contact, the email addresses would have the contact id to relate them.

A selector on the email Entity would have to do something like this to get the related emails for the selected contact.

export const getSelectedEmails = createSelector(
   getEmailEntities,
   getEmailIds,
   getSelectedContact,
   ( entities, ids, contact) => {
      return ids.filter(i => entities[i].contactid === contact.id? true: false)
})

Right back to filtering long arrays to extract a few matching items.

That is a pretty simple situation. In real life it gets complicated quickly. Maybe you have an attribute property on the email, where the email address is tagged various ways; receive only, business, personal, project specific, etc. You might want to filter on those attributes as well. You might want to do a many to many relation where the same email address belongs to many people.

This is what I needed. So I came up with another property of the Entity State.
interface Keys {
   [key: string]: { [id: string| number]: ids[] }
}

This is how the keys are extracted. It looks similar to the stock Entity
export const adapter: EntityRelationAdapter<InventoryData> = createEntityRelationAdapter<InventoryData>({
  selectId: Inventorydata => Inventorydata.tid,  selectKey: {
    item: InventoryData => InventoryData.itemtid,    vendoritem: InventoryData => InventoryData.vendorinvoice,    resourcetype: InventoryData => InventoryData.resourcetype,    }
});
 selectKey has functions that extract keys, which creates a list of keys. So in your selector you would do something like keys['item'][itemid] to get a list of ids that are related to the item data.

As with the Entity the array of ids is created as each item is added to the entity state. I haven't implemented a sort function, mostly because I don't need it, and typically the related lists are short and easy to sort in a selector.

Take a look. It is useful to me.

https://github.com/derekkite/relation-entity

This page is powered by Blogger. Isn't yours?

Subscribe to Posts [Atom]