A little bit of background is in order
In Ngrx there is only one store. This is a giant global variable that any reducer can affect. In large programs, it'll be hard to reason over. We don't wish this on future maintainers.
How do we make it easier to reason over?
It is easier to comprehend if the store is partitioned into different features so that actions, reducers, and state that only pertain to a single feature set, for instance logging in, shopping cart, et c. are maintained in different areas/features.
Store
+-------------------------------+ {
| Auth | "auth": { username: "bob", country: "us" },
| Cart | "cart": { items: [] }
| ... | }
+-------------------------------+
As you probably already know, createSelector
is used to select something from the store.
export const selectUserName = createSelector(
(store: any) => store.auth as Auth, // in real life don't use any, see discussion later
(auth: Auth) => auth.username)
We can simplify this into
const selectAuth = createSelector((store: any) => store.auth)
export const selectUserName = createSelector(selectAuth, auth => auth.username)
Further simplifying into
const selectAuth = createFeatureSelector<Auth>('auth')
export const selectUserName = createSelector(selectAuth, auth => auth.username)
There is an alternate way to writing this if you love rxjs
import { pipe } from 'rxjs'
const selectAuth = createFeatureSelector<Auth>('auth')
export const selectUserName = pipe(selectAuth, auth => auth.username)
Optional - discussion on 'any'
In highly complex applications, whenever you add another feature, the root (global) store's type changes. This can get unwieldy over time.
// define in state.js
type RootState = {
auth: Auth
cart: Cart
// keep adding feature
// ad infinitum...
}
// define in a auth.selector.js
const selectAuth = createSelector((store: RootState) => store.auth)
In this sense, using createFeatureSelector
is preferred because you don't need to define RootState
at all.