Memoization with Selectors in NgRx

Keerti Kotaru
The Startup
Published in
5 min readApr 28, 2020
Image by Vencki Vista

The prior article on Memoization in JavaScript discusses caching results of a long running JavaScript function. Majority of the functions that run in a browser finish fairly quickly, return in milliseconds. That begs the question, what are the real-world use cases for memoization in JavaScript? This article describes one such use case.

In Redux, store maintains the application state. Structuring the store is a critical aspect of designing the front-end application (involving Redux). It can be difficult, considering the NgRx store can be large. It maintains state of multiple features in an application. Of course, the application has many components. Each component presents parts of the state. Selectors help transition state to component friendly, view-model.

Selecting a piece of data in the entire data structure can be costly. It’s common that components tap into the store repeatedly for the data. Among other things one of the features of NgRx selectors is Memoization. They cache the selected part of the application state and return the results quickly. We don’t go through the entire store’s data structure for repeated calls.

To-do sample

Consider very simplistic to-do application as a use case. User can create, show a list of to-dos and mark one or more to-dos complete. The NgRx store has the entire list of to-dos. See the following figure-1 depicting NgRx Store, selectors and components in a to-do application.

Figure-1: Use case- To-do application with ngrx

There are two components in the sample. The first component, To-do list shows all to-dos, allow users to edit a to-do (mark it complete), filter to-dos based on complete/incomplete status. The second component, Create-todo creates a new to-do by allowing user to type in a to-do and save. Each component dispatch one or more actions for creating and retrieving the to-dos.

An action invokes appropriate reducer function, which manages and returns state. In our example, a list of to-dos is the state returned by the store. As application complexity grows, complexity of the state grows.

Selectors

NgRx selectors help retrieve subset of the state, specific to the feature or the component in question. They are pure functions that consistently return the result only dependent on the input arguments. Read official documentation on NgRx Selectors here.

Memoization is one of the features of Selectors. Consider the following code snippet-1 with selector functions. The first selector getActiveTodos returns to-dos that are not marked complete. The second selector getAllTodos returns the complete list of to-dos.

export const todos = (state: State) => {
return state.todos;
}
export const getActiveTodos = createSelector(
todos,
state => {
console.log("%cInvoked getActiveTodos()", "background: skyblue; color: purple; font-size: 14pt");
return state.filter(item => item.isComplete === false)
});
export const getAllTodos = createSelector(
todos,
state => {
console.log("%cInvoked getAllTodos()", "background: lightgreen; color: purple; font-size: 14pt");
return state;
});
Snippet-1: Selectors for returning to-do

Notice, selector is created on a function todos(). It accepts state as an input argument. Also notice the console log. Every time selector is run and state is returned from the reducer, the log is printed. When we run this sample, log prints only once, till it’s reset. It is because the selectors memoize the results. See figure-2 video demonstrating this very aspect.

Next, let’s look at how the selector is used in a component. The to-do list component subscribes to getActiveTodos selector on init. See the code snippet-2 below.

@Component({
selector: 'app-todo-list',
templateUrl: './todo-list.component.html',
styleUrls: ['./todo-list.component.css']
})
export class TodoListComponent implements OnInit {
ngOnInit(): void {
this.todos$ = this.store
.pipe(
select(selectors.getActiveTodos),
filter(val => val !== undefined)
);
}
/// ...
/// rest of the component code
Snippet-2: getActiveTodos selector in the component

Creating a new to-do resets the selectors’ cache. The create to-do component dispatches an action, which updates the state. As the state has changed, selectors recompute. They do not use the cached result anymore. Consider the following code snippet, dispatching an action from create to-do component.

import { createTodo } from '../ngrx/todo.actions';@Component({
selector: 'app-create-todo',
templateUrl: './create-todo.component.html',
styleUrls: ['./create-todo.component.css']
})
export class CreateTodoComponent implements OnInit {
todoText: string = "";
constructor(private store: Store<Todo>) { }
ngOnInit(): void {}
createClick(){
this.store.dispatch(createTodo ({
id: 0,
title: this.todoText,
isComplete: false
}));
}

}

How did it reset the cache? Selectors are “reactive”. They recompute as the state changes. This ng-conf video, explains it very well. However, a simplistic reason would be, selectors are pure functions. A change in arguments reset the cache. Notice, code snippet-1. State is an input argument to the selector. A change in state results in change to this argument, which resets the selector.

Remember, part-1 provides a JavaScript function example to store cache separately for various function arguments.

Consider the following video showcasing memoization. Keep an eye on Chrome console for the messages from selectors (messages in color). As described above, it demonstrates memoization of the result from a function / selector. It does not fetch state from reducer for repeated invocations. The video also showcases change in state (by creating a new to-do) recomputes selector. And hence it prints console statement again in the selector function.

Figure-2: To-do application in action

About the code sample

Clone and run the following code sample, https://github.com/kvkirthy/ngrx-memoization-sample/.

Notice, the sample uses various components from Angular Material library. It provides Material Design implementation for Angular applications. Material Design is a Google’s design language that includes tried and tested user experience paradigms. Check out my books on Angular Material here.

Code organization is as following,

  • As described earlier, the sample includes two components a) src/app/todo-list and b) src/app/create-todo
  • NgRx actions, reducer and selectors are included under src/app/ngrx/ in the following files a) todo.actions.ts b) todo.reducer.ts and c) todo.selector.ts

--

--