NgRx store selectors
Application state can be seen as tree that can be serialized to JSON. We already looked at how can we manipulate the state data - that’s the easy part.
Now to get data out of the state tree, reminder state data most of the time is complex nested object, we have to traverse it to find our property of interest. To make this process easy selectors were introduced.
Selectors are pure function which are used for obtaining store state.
To get slice of data from store we use the store.select
method.
store.select('todoState');
The string represents the name of a slice of state in the store, and we can expect this function to return data corresponding to our todoState
property.
Note: select gives you back a slice of data from application state wrapped into an Observable. And you can use this Observable which wraps your property of interest and subscribe to that Observable to consume the data and listen for any further changes.
store.select
can also take a function instead of a string, which takes a slice of state and returns a property from the state.
store.select((state: TodoState) => state.todos);
Both of these approaches represent the concept of a selector.
As the application becomes bigger the state tree grows deeper, it would become complex to get the desired property of state out of the store.
As a result, the transformation logic becomes complex. Moreover, this will pollute the component. So, to keep the components lean and decoupled from the store we use NgRx selectors which is basically a function that gets slices of data from state.
When using the createSelector
and createFeatureSelector
functions @ngrx/store
keeps track of the latest arguments in which your selector function was invoked. Because selectors are pure functions
, the last result can be returned when the arguments match without reinvoking your selector function. This can provide performance benefits, particularly with selectors that perform expensive computation. This practice is known as memoization
.
Memoization
is just one advantage of using createSelector
and createFeatureSelector
. Selectors provide many features when selecting slices of state.
- Portability
- Composition
- Testability
- Type Safety
Note: It is not compulsory to use NgRx selectors to get slices of data. NgRx selectors as we have seen offers amazing features we can’t just ignore.
Using selector in our app
Create a new action file viz todo.selector.ts
inside store
folder of todo
.
Getting single piece of state i.e. todoState
import {todoFeatureKey, TodoState} from './todo.reducer';
import {createFeatureSelector, createSelector} from '@ngrx/store';
export const selectTodoState = createFeatureSelector<TodoState>(todoFeatureKey);
export const selectTodos = createSelector(selectTodoState, (state: TodoState) => state && state.todos);
Now let’s use selectTodoState
in our TodoComponent
to listen for changes.
import {filter, takeUntil} from 'rxjs/operators';
import {select, Store} from '@ngrx/store';
export class TodoComponent implements OnInit, OnDestroy {
private unsubscribe = new Subject<void>();
constructor(private readonly store: Store) {
}
ngOnInit(): void {
this.store.pipe(
select(selectTodos),
takeUntil(this.unsubscribe)
).subscribe(todos => {
this.todos = todos;
});
}
ngOnDestroy(): void {
this.unsubscribe.next();
this.unsubscribe.complete();
}
}
Basically we are using RxJS pipeable operator
that takes an Observable as its input and returns another Observable. Inside pipe
we are combining two operators viz select
and takeUntil
.
select
as discussed before gives you back a slice of data from application state wrapped into an Observable. takeUntil(this.unsubscribe)
means take the state value or listen to state value or subscription should be active until unsubscribe
is not complete. unsubscribe
is another observable which we complete when TodoComponent
is destroyed.
To summarize, we are subscribing to selectTodoState
until TodoComponent
is destroyed or unsubscribe
is complete.
You can add other RxJS operators if you need. For example, to only select the state which has length greater than zero since we know our todo is an array.
this.store.pipe(
select(selectTodos),
filter(state => state.length > 0),
takeUntil(this.unsubscribe)
).subscribe(todos => this.todos = todos);