We dont want one big, flat glob of state. Rather we organize our state into feature slices. This keeps the state associated with its feature module and make it easier to find and manage the state.
Defining an interface for a slice of state
How do we strongly type this state ?
With typescript, we use interface. For each slice of state we define an interface that describes the structure of that state.
Extending the state interface for lazy loaded features
The Product and app state will be changed like below for lazy loaded state
Setting Initial Value
To assign this initial state, we take advantage of javascript’s default functional parameter syntax.This syntax allows us to initialize a function parameter with a default value.
Now the store is initialized and the reducer is first called, these initial values are assigned and our store is never undefined.
We no longer need to check our state before accessing it, because now it is predictably defined, so we can delete this if condition and simplify our arrow function.
So far our components subscribe to the store, selecting the entire products slice of state. There are a few issues with this approach.
1. Hard-coded string
2. Knows the store structure
We explicitly retrieve a property from the store (products.showProductCode), making assumptions about the store structure. That means if we ever change the structure of our store, reorganizing it into sub-slices, for example we have to find every select and update its code.
Lastly, this code watches for changes to any property in the products slice of state, so this code is notified even if the showProductCode property was not changed. How do we make this better? SELECTORs.
A Selector is a reusable query of our store. It is basically like a stored procedure for accessing our in-memory state information. Selectors allow us to keep one copy of the state in the store, but project it into different shapes, making it easier to access by our components and services. Our components use the selector to select state from our store, adding a level of abstarction between our stores structure and our component.
There are several benefits to using selectors.
1.They provide a strongly typed API for the components to use. We dont have to refer to slices of state with hard coded strings.
2.They decouple the store from the components so the components dont need to know about the structure of the store. This allows us to reorganize or split up the state differently over time without updating every component that accesses it.
3.selectors can encapsulate complex data transformations, making it easier for the components to obtain complex data.
4. They are reusable , so any component can access the same bit of state the same way.
5. And Selectors are memoized, that’s a fancy word meaning that the selectors returned value is cached and won’t be re-evaluated uless the state changes. This can improve performance.
So what is a selector exactly ? It is a function that drills into the store and returns a specific bit of state.
There are two basic types of selector functions provided by the NgRx library
1. The first type is a “createFeatureSelector”. This function allows us to get the feature slice of state simply by specifying its feature name. We strongly type the return value using the generic argument
const getProductFeatureState = createFeatureSelector
When executed, it selects the specific feature slice of state. We don’t export this constant so it can only be used where it is defined.
2. The second type of selector function is a createSelector.
This function allows us to get any bit of state by composing selectors to navigate down the state tree. Here we pass the feature selector function in as the first argument to this selector. The last argument is a projector function that takes in the state (state=>) retruned from the prior arguments, which in this case is the products slice. We can then filter, map , or otherwise process the state to retrun the desired value(state.showProductCode). In this case we map to the showProductCode property to return its value. We assign this fuction to an exported constant(getShowProductCode) so we can use the selector from our components. When executed, it selects a specific bit of state and returns its value. In this case, it returns true.
By using selectors in our components, if our stores structure ever changes, we can modify these selectors to access that new structure without changing any of the components that use them. One important thing to note here, a selector should be a pure function. That is to say that given the same input, the function should always return the same output with no side effects.
We use the selector in our components with the select operator or select method. Here is a select operator with a hardcoded string. We used this technique in our product-list component to select the showProductCode flag. The select argument is a string “select(‘products’)” representing the name of the slice of state. The select returns the data in the store associated with this name.. In this case, it returns the entire products slice of state. We then manually navigate down (“products.showProductCode”) from the products slice to the desired property, so our code must know how to navigate the state tree. To use a selector instead, we replace the hardcoded string with a reference “select(fromProduct.getShowProductCode)” to the desired selector function. This then returns the Boolean flag value(true – in this case), so we simply assign that value to our local property “this.displayCode = showProductCode”.
This code knows nothing about our stores structure. We import * from our reducer file. To make the set of selectors easier to access, we define a namespace “as fromProduct” for those exported members using the “as” keyword. We then reference each selector using “fromProduct” . Note that you don’t have to use this namespacing technique, you can instead import each selector individually from your reducer file.
Building Selectors
1. Define a constant getProductFeatureState, we dont export this constant , so that way it can be used inside of this file.
2. Build Feature selector using the NgRx createFeatureSelector function, specifiying our interface as the generic argument.
3. We pass in the name of the feature slice of state.We defined this name in the product module.
4. Now we are ready to add a selector for any state property that our application may require.
5. Lets add a selector for our showProductCode property. We want our general selectors to be available anywhere in the application, so we export them as constants.Will name this selector “getShowProductCode”. We then build the selector using the “createSelector” function. The first argument to this fucntion is the selctor required to retrieve the desired bit of state. For this example, it is the feature selector “getProductFeatureState” function. The last argument is the projector function. The projector function get the result of the selector functions, which in this example is the product slice of state. We then manipulate that slice as need to return the desired property value “state.showProductcode”. In this case, we return the value of the showProductCode property.
6. Note the order of these constants matter. Since this is a simple code file, not a class, each constant must be after any constant it references. Since this constant “getShowProductCode” references the “getProductFeatureState” constant, it must be after it in the file.
const initialState: ProductState = {
showProductCode: true,
currentProduct: null,
products: []
}
7. Now let’s add selectors for the currentProduct and array of Products properties.
export const getCurrentProduct = createSelector(
getProductFeatureState,
state => state.currentProduct
)
export const getProducts = createSelector(
getProductFeatureState,
state => state.products
)
They both select a property value from our store without any additional processing.
Using Selector : Now let’s see how to use one of these new selectors
We use a selector to watch for changes to specific state properties in our store. Here in the product-list. We currently select the product slice of state using a hardcoded string.
this.store.pipe(select('products')).subscribe(products => this.displayCode = products.showProductCode);
Let’s modify this code to instead use one of our new selectors. In the select operator, we change the hardcoded products to our selector. We can use the namespace we defined on the import statement to see the list of all avilable selectors. We will use getShowProductCode. Instead of returning the entire products slice of state, it now retruns only the showProductCode flag, so we change the argument here and the assignment here. That simplifies things and ensures that if the store structure is ever changed, we won’t have to change our component.
Composing selector
We have already seen how to compose selectors from other selectors. Each selector we created with createSelector is composed from the createFeatureSelector. But we are not limited to one.
We can compose multiple selectors to build a single selector.Say that instead of storing the currentProduct in this store, we retain the ID of the currentProduct(below).The selector for this property would look like this.
But our component still wants the current product, not just the ID. To do that, we further compose our current product selector. Here we pass in both the “getProductFeatureState” selector and the “getCurrentProductId” selector. The result of each selector “(state, currentProductId)” is provided to the projector function in the order they are defined.
So the first argument here is the state returned from the “getProductFeature” selector.
The second argument is the currentProductId retruned from the “getProductId” selector.
Our projector fuction can then use both arguments to return the appropriate product. (state.products.find(p => p.id === currentProductId)). This code uses the array find method to find the desired product in the array using the currentProductId provided from the store.
Why use composition here and not simply reference the ID from the state like this ?
export const getCurrentProduct = createSelector(
getProductFeatureState,
(state) => state.products.find(p => p.id === state.currentProductId)
);
It comes back to encapsulation. By separately defining a selectotr for each bit of state and then composing the selectors, the structure of the store can be changed over time with minimal impact on the code.
Bottom line, when building selectors, define one for each bit of state that is accessed from the store and compose them as needed by your components and services.
Build selectors to define reusable state queries against the store state. These selectors decouple the component from the store and can encapsulate complex data manipulation