Photo by Kaizen Nguyễn on Unsplash
How to Use NgRx Selectors in Angular
How NgRx Selectors Can Improve Your Angular App
In NgRx, when we want to get data from the store, the easiest way is by using store.select
. It allows us to get any slice of the state. Yes, it sounds funny, but any slice returns an Observable<any>
.
For example:
It is flexible but also risky because, what happens if our state
structure changes? The best way to fix this is by using Selectors
. Let's play with them!
NgRx Selectors
The Selectors help us get slices of the store state with type safety. They act like a mirror of our state, making it easy to use anywhere and allowing us to avoid repeating the same code in our application.
NgRx provide two functions createFeatureSelector()
and createSelector()
to create selectors.
The createFeatureSelector
function allows us to define a type-safe slice of our state using our state definition.
export const selectHomeState = createFeatureSelector<HomeState>('home');
The createSelector
function uses the featureState
as the first parameter and a second function to get the featureState
and pick the slice.
export const selectLoading = createSelector(
selectHomeState,
(homeState) => homeState.loading
)
We already know the NgRx selector functions, so let's use them and have some fun 🔥!
Creating Selectors
It's time to start using createFeatureSelector
and createSelector
in our project. We continue with the initial project of NgRx, clone it, and switch to the action-creators
branch.
git clone https://github.com/danywalls/start-with-ngrx.git
git switch action-creators
Open the project with your favorite editor, and create a new file src/app/pages/about/state/home.selectors.ts
. Next, import the createFeatureSelector
and createSelector
functions. Use createFeatureSelector
with the HomeState
interface to create selectHomeState
.
export const selectHomeState = createFeatureSelector<HomeState>('home');
After that, use selectHomeState
to create selectors for players
, loading
, and acceptTerms
.
export const selectLoading = createSelector(
selectHomeState,
(homeState) => homeState.loading
)
export const selectPlayers = createSelector(
selectHomeState,
(homeState) => homeState.players
)
export const selectAcceptTerms = createSelector(
selectHomeState,
(homeState) => homeState.acceptTerms,
)
We can also compose selectors. For example, if we want to know when the players
have data and the user has accepted the terms (acceptTerms
), we can create selectAllTaskDone
. This combines the selectPlayers
and selectAcceptTerms
selectors to check if all tasks are done.
export const selectAllTaskDone = createSelector(
selectPlayers,
selectAcceptTerms,
(players, acceptTerms) => {
return acceptTerms && players.length > 0;
}
)
The final code in home.selectors.ts
looks like this:
import {createFeatureSelector, createSelector} from "@ngrx/store";
import {HomeState} from "./home.state";
export const selectHomeState = createFeatureSelector<HomeState>('home');
export const selectLoading = createSelector(
selectHomeState,
(homeState) => homeState.loading
)
export const selectPlayers = createSelector(
selectHomeState,
(homeState) => homeState.players
)
export const selectAcceptTerms = createSelector(
selectHomeState,
(homeState) => homeState.acceptTerms,
)
export const selectAllTaskDone = createSelector(
selectPlayers,
selectAcceptTerms,
(players, acceptTerms) => {
return acceptTerms && players.length > 0;
}
)
Okay, with the selectors ready, it's time to refactor home.component.ts
to use them. Import each selector from home.selectors.ts
.
Note: Remove the
toSignal
function and usestore.selectSignals
to automatically transform the selectors' observables into signals.
public $loading = this._store.selectSignal(selectLoading);
public $players = this._store.selectSignal(selectPlayers);
public $acceptTerms = this._store.selectSignal(selectAcceptTerms);
Finally, create a new variable to use the composable selector selectAllTaskDone
.
public $allTasksDone = this._store.selectSignal(selectAllTaskDone);
Update home.component.html
markup to use the $allTasksDone
signals in the template.
@if (!$allTasksDone()) {
Wait for the players and accept terms
} @else {
<h2>Everything done!🥳</h2>
}
Save the changes, and everything will continue to work 😄. To test the composed selectors, when playersLoadSuccess
is triggered and you click on acceptTerms
, the message "Everything done!" will be shown!
Conclusion
We learn how to use selectors
to retrieve and manage state slices in NgRx, instead of directly using store.select
, getting the benefits of type-safe selectors with createFeatureSelector
and createSelector
and composing selectors.