Platform: Selecting one entity by id with adapters

Created on 26 Jun 2018  路  5Comments  路  Source: ngrx/platform

I'm submitting a...

[ ] Regression (a behavior that used to work and stopped working in a new release)
[ ] Bug report
[x] Feature request
[ ] Documentation issue or request

What is the current behavior?

The entity adapter is great for managing a list of ng-components of the same type (like a list of users). Currently you can get some nice selectors out of the box:

// user.reducer.ts

... 

export const {
  // select the array of user ids
  selectIds: selectUserIds,

  // select the dictionary of user entities
  selectEntities: selectUserEntities,

  // select the array of users
  selectAll: selectAllUsers,

  // select the total user count
  selectTotal: selectUserTotal,
} = adapter.getSelectors();

Unfortunately, I very often need to select just one entity by its id (that never changes). A naive implementation of this requirement would be:

// user.container.ts

...

this.user$ = this.store$.pipe(select(fromUser.selectUserEntities))
    .pipe(
       map((entities: { [id: string]: User}) => {
         return entities[this.idOfUserIWantToSelect];
      }),
);

But in this case this.user$ is updated every time anything changes in any entity (not only the one I'm interested in). If this.user$ is passed using async pipe, this implies a useless refreshing of the ui in all user-dummy-components.

Describe any alternatives/workarounds you're currently using

The solution (at least in my case) is to use something like this:

// user.reducer.ts

...

export const getEntityById = (id: string) => (state: State) => state.entities[id];
export const getUserEntityById = (id: string) => createSelector(getUserState, getEntityById(id));
// user.container.ts

...

// idOfUserIWantToSelect never changes
this.user$ = this.store$.pipe(select(fromUser.getUserEntityById(idOfUserIWantToSelect)));

This way the selector is correctly memoized. I think it would be very helpful to have something ready out the box that devs can use to avoid writing the same code over and over.

Other information:

This is a simple demo that illustrates the two behaviors described above.
ngrx-adapter-multi-comp

If accepted, I would be willing to submit a PR for this feature

[ ] Yes (Assistance is provided if you need help submitting a pull request)
[ ] No

Entity enhancement

Most helpful comment

I think with the introduction of createselector with props and ngrx 6.1 (I think #1175 ), this issue can be closed: it seems to me it is working fine!

// user.reducer.ts

... 

export const {
  // select the array of user ids
  selectIds: selectUserIds,

  // select the dictionary of user entities
  selectEntities: selectUserEntities,

  // select the array of users
  selectAll: selectAllUsers,

  // select the total user count
  selectTotal: selectUserTotal,
} = adapter.getSelectors();


export const getUserState = createFeatureSelector<UserState>('users');

export const getUserEntities = createSelector(getUserState, selectUserEntities);

export const getUserEntityById = () => {
  return createSelector(
    getUserEntities,
    (entities: Dictionary<User>, props: { id: string }) => {
      return entities[props.id];
    },
  );
};

All 5 comments

Hey @mauriziocescon, really like that idea. I have implemented this selector over and over again for several entities/projects.
I made a quick implementation, lets discuss if it makes sense or not. Any other thoughts?

1152 is related to this issue.

I think with the introduction of createselector with props and ngrx 6.1 (I think #1175 ), this issue can be closed: it seems to me it is working fine!

// user.reducer.ts

... 

export const {
  // select the array of user ids
  selectIds: selectUserIds,

  // select the dictionary of user entities
  selectEntities: selectUserEntities,

  // select the array of users
  selectAll: selectAllUsers,

  // select the total user count
  selectTotal: selectUserTotal,
} = adapter.getSelectors();


export const getUserState = createFeatureSelector<UserState>('users');

export const getUserEntities = createSelector(getUserState, selectUserEntities);

export const getUserEntityById = () => {
  return createSelector(
    getUserEntities,
    (entities: Dictionary<User>, props: { id: string }) => {
      return entities[props.id];
    },
  );
};

Here is the demo above with the new functionalities
new-ngrx-adapter-multi-comp

Great job, thanks!

Ok, I'm glad you like it 馃槃 ; I'll close this issue and the according PR.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

gperdomor picture gperdomor  路  3Comments

Matmo10 picture Matmo10  路  3Comments

oxiumio picture oxiumio  路  3Comments

ghost picture ghost  路  3Comments

hccampos picture hccampos  路  3Comments