Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Selecting one entity by id with adapters #1156

Closed
mauriziocescon opened this issue Jun 26, 2018 · 5 comments
Closed

Selecting one entity by id with adapters #1156

mauriziocescon opened this issue Jun 26, 2018 · 5 comments

Comments

@mauriziocescon
Copy link

mauriziocescon commented Jun 26, 2018

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

mgred added a commit to mgred/platform that referenced this issue Jul 12, 2018
* add a new selector 'selectById' to EntitySelector (model)
* implement selector, function to take id string || number
* add tests

See: ngrx#1156
@mgred
Copy link
Contributor

mgred commented Jul 12, 2018

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?

mgred added a commit to mgred/platform that referenced this issue Jul 12, 2018
@mauriziocescon
Copy link
Author

#1152 is related to this issue.

@mauriziocescon
Copy link
Author

mauriziocescon commented Aug 17, 2018

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];
    },
  );
};

@mauriziocescon
Copy link
Author

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

Great job, thanks!

@timdeschryver
Copy link
Member

timdeschryver commented Aug 17, 2018

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants