Skip to content
Angular Essentials
GitHub

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);