Computed Values with RxJS and @ngrx/store
July 25, 2016

Cross-post from the Rangle.io blog.

Introduction

@ngrx/store is a popular store architecture for Angular 2. It promotes one-way data binding for components:

  • Components subscribe to updates from the store
  • Components dispatch events to the store
  • Reducers receive dispatched events and modify the store data structure
  • …and repeat.

We’ve used @ngrx/store on several Angular 2 projects at Rangle. @ngrx/store was built specifically for Angular 2 and leverages the wonderful JavaScript Reactive Extensions library (RxJS). This allows updates to the store to be consumed as observable streams in our components. We’ve previously written how Observables are central to Angular 2.

@ngrx/store in principle is very similar to Redux and the concepts should be familiar to someone with knowledge of Redux. Most Redux best practices have an equivalent in @ngrx/store and we’ll be covering one of those today: computed values.

Reselect with Redux

First let’s contrast with the best practices in Redux. Reselect is a selector library for Redux. Selectors are memoized functions used to compute derived values from the store. Being memoized implies values are only computed when the inputs to the selector function change. With Reselect, we don’t need to put computed values in the store. We can instead store the minimal state and recompute derived data only when necessary. We’ve covered this topic recently in an article titled Improving React and Redux performance with Reselect.

We can achieve a similar effect with @ngrx/store using built-in RxJS’s methods. I’ll illustrate on an example application.

Example Application

The examples below use the following monotonically increasing counter store instrumented into an Angular 2 TypeScript application:

counter.reducer.ts

import {ActionReducer, Action} from '@ngrx/store';

export const counter: ActionReducer<number> = (state: number = 0, action: Action) => {
  switch(action.type) {
    case 'INCREMENT':
      return state + 1;
    default:
      return state;
  }
};

bootstrap.ts

import {bootstrap} from '@angular/platform-browser-dynamic';
import {App} from './app';
import {provideStore} from '@ngrx/store';
import {counter} from './reducers/counter';

bootstrap(App, [provideStore({counter})]);

Note: The examples in this blog post can be found on Plunker.

Computed values with RxJS

In the examples below we’ll be selecting a value from the store and performing an operation on it. We need the store instance to do this, which can be added to a component through Angular 2‘s dependency injection. The instance of the store is technically a BehaviorSubject which inherits the methods from Observable.

select shares the same performance characteristics as Reselect, as it uses the distinctUntilChanged operator on the observable it returns (see here). This effectively memoizes any subscribers to select, similar to Reselect.

Map

For computing a value from the most recent store data, we can combine @ngrx/store’s select method with RxJS’s map operator. map will receive the latest value from the store and apply a transformation to it, similar to JavaScript’s map. In my experience map is the most common operation used when computing values from the store.

import {Component} from '@angular/core';
import {Store} from '@ngrx/store';
import {Observable} from 'rxjs/Observable';
import {IStore} from './store.interface';
import 'rxjs/Rx';

@Component({
  selector: 'power',
  template: `
  <div>
    <h2>Power (map example)</h2>
    <ul>
      <li>2<sup></sup> = </li>
    </ul>
  </div>
  `
})
export class PowerComponent {
  counter$: Observable<number>;
  power$: Observable<number>;

  constructor(private store: Store<IStore>) {
    this.counter$ = this.store.select('counter');
    this.power$ = this.counter$.map((value) => Math.pow(2, value));
  }
}

Scan

RxJS’s scan operator is similar to reduce in JavaScript, except reduce acts on arrays while scan acts on streams. Streams differ from JavaScript arrays in that values are added to them over time. They are not operated on entirely at once. scan allows us to operate on all values of a stream over time and return a singular value. This value is updated when new values are added to the store. Here we’ll use scan to compute the factorial of the counter in the store:

import {Component} from '@angular/core';
import {Store} from '@ngrx/store';
import {Observable} from 'rxjs/Observable';
import {IStore} from './store.interface';
import 'rxjs/Rx';

@Component({
  selector: 'factorial',
  template: `
  <div>
    <h2>Factorial (scan example)</h2>
    <ul>
      <li>! = </li>
    </ul>
  </div>
  `
})
export class FactorialComponent {
  counter$: Observable<number>;
  factorial$: Observable<number>;

  constructor(private store: Store<IStore>) {
    this.counter$ = this.store.select('counter');
    this.factorial$ = this.counter$.scan((acc, value) => acc * value);
  }
}

Selecting across keys

You’ll notice in our previous examples computed values were based on one key from our store (through select('keyName')). What if we want a value derived from multiple store keys?

We can pass @ngrx/store’s select a function, allowing us to select arbitrary subsets of store data instead of a single key. For example, if our store also had a name key and we wanted to grab them both, we could do the following:

this.counterAndNameSelect$ = this.store.select((state) => {
  return {
    counter: state.counter,
    name: state.name
  };
});

Additionally, RxJS allows us to combine multiple streams into one by using the combineLatest operator. This can be used as alternative to passing select a function. This is equivalent to the select above:

this.counterAndNameCombine$ = this.store.select('counter')
  .combineLatest(this.store.select('name'), (counter, name) => {
    return {
      counter,
      name
    };
  });

Use with Angular 2

Angular 2 treats RxJS streams as first class citizens. Observable’s can be leveraged directly in HTML templates by using Angular’s async pipe. In our components, we can save Observables as public values and access them in our templates without having to manually subscribe. This is the approach we followed in the above examples.

Resources

@ngrx/store puts the full power of RxJS is as your disposal! I’ve only shown as few ways this power can be harnessed. For more information on @ngrx, Redux and RxJS, see the following resources:

Thanks to the support squad at Rangle.io, namely Evan Schultz and Cosmin Ronnin for reviewing this.

programming ngrx rxjs redux