"How can I select an entity from the store by its id?"
This question popped up several times lately and in this post, I’ll provide some suggestions to create a selector that works with parameters, so let’s not waste any time, and let’s get started!
The following will be more or less a copy of the NgRx docs.
As of NgRx 6.1 selectors also accepts an extra
props argument. Which means you can now define a selector as the following:
Inside your component you can use the selector as usual but you can define the
Keep in mind that a selector is memoized, meaning it will cache the result from the last parameters. If you would re-use the above selector but with another
props value, it would clear the previous cache. If both selectors would be used at the same time, as in the example below, the selector would be invoked with every
props value, thus the memoization would be more or less useless.
Every time the counter value changes in the above example, the selector would be invoked 2 times, one time for
counter2, the other time for
counter4. To allow memoization we can use a factory function to create the selector.
And in our component we can invoke the factory function
fromRoot.getCount() to create a new selector instance for each counter, allowing each instance to have its own cache.
If the parameter doesn’t change over time we can use a factory function
selectCustomer which has a parameter
id and returns a selector. Making it possible to use the
id parameter in our selector to retrieve the customer, resulting in the following:
We can then call
selectCustomer in the component and pass it an
id parameter is dynamic we can create a selector that instead of returning customers, returns a function which expects a parameter
id. The selector becomes:
And in our component:
For this example, I’m also going to show the HTML, because it’s maybe not that straight forward. Because the selector returns a function now, we can call it like a normal function in our HTML:
To overcome this syntax inside the HTML we can also solve this with the RxJS
map operator, as mentioned by Juliano Pável.
We can also hook into the RxJS stream and map/filter our selector result by using one or more RxJS operators. In the snippet below, we select all the customers from the store and retrieve the current customer from it.
This approach does have a few drawbacks:
- it's different than other selectors, the projection logic is spread in the selector and in the component
- it's harder to test
- it's not that performant but in most cases, you won't notice the difference
While the above examples work, for me retrieving data from the store like this feels a bit dirty and I consider it a bad practice in most cases. In my opinion, it's better to persist this "filter" state in the store, in our example it would mean that the
id parameter would exist somewhere in the store.
This has the benefit that we're going to be fully reactive. When the filter's state changes, the selectors will be re-invoked (with the updated state), and our component will receive the new filtered state.
To implement this we must define actions that we can dispatch whenever a filter (the customer's id) changes. For example when the user clicks on a customer or when the user navigates to a customer's page, we dispatch an action. These actions are updating the
selectedCustomerId property in the store state.
We also have to create a selector
selectSelectedCustomerId to select the
selectedCustomerId from the state. Because selectors are composable we can use both selectors to get the selected customer in the
The selectors looks like this:
In the component, we can consume the selector the ‘usual way’ without having to worry about the customer's id.
In the future when we have another filter, or when we add an action that changes the filter we don't have to worry about displaying the correct data in the component. It will just work because this selector is entirely driven my the Store's state.
Another possibility would be to use @ngrx/router-store, this module connects the route with the store. In other words, all the route information will be available in the store thus also in the selectors. After installing the
ngrx/router-store module and having it imported in our
AppModule, we’ll first have to create a selector
selectRouteParameters to retrieve the route parameters (
customerId in our case). Thereafter we can use the created selector in
selectCurrentCustomer to select the current customer. This means that when a user clicks on a link or navigates directly to
/customers/47, she or he would see the customer’s details of customer 47. The selector looks roughly the same:
And the component remains the same (except for the selector’s name):
In my opinion the code we ended up with looks cleaner than the code we started with. I hope this was useful if you were looking for a way to parameterize your selector.
- NgRx Selectors How to stop worrying about your Store structure — David East & Todd Motto @ ng-conf 2018 - the last part will show you an example with
- The reduxjs/reselect docs about parameterization with
Please consider supporting me if have you enjoyed this post and found it useful: