NgRx: Fun With `createSelectorFactory()`
Tags: angular, ngrx, rxjs, selectors
Published: 2020-12-21 17:44:23

---
title: NgRx: Fun With `createSelectorFactory()`
published: true
description: We explore what a selector factory is in NgRx and how we can use it for some practical purposes in our applications.
tags: Angular, NgRx, RxJS, selectors
cover_image: https://i.imgur.com/BcWiqpJ.png
---

This article is an investigation of some of the more complex features of the `@ngrx/store` library. For some of the basics of NgRx and the redux pattern, be sure to check out the [NgRx docs](https://ngrx.io/docs) as they're excellent!!

*** Also a quick disclaimer regarding the examples given in this article! Be sure to watch [Mike Ryan's talk about action hygiene and creating an event-driven store](https://www.youtube.com/watch?v=JmnsEvoy-gY), as opposed to command-driven store. As this article is a dive into the mechanics of `@ngrx/store`, we'll be using command-driven actions in our examples, but in no way should this be taken as me advocating these types of actions in your real-world apps! ***

# What Happens When You Call `createSelector()`

![The NgRx Diagram](https://ngrx.io/generated/images/guide/store/state-management-lifecycle.png)

The job of a selector is to 'query' or 'derive' data from the object held in our `Store`. Assuming you had set up a Typescript `Interface` or `Type` to represent the shape of your `Store`, you'd probably expect this `selector` code to look something like this:

```ts
export interface FeatureState {
  array: string[];
}

export interface State {
  featureNameplace: FeatureState;
}

export function selectArray(state: State) {
  return state.featureNameplace.array;
}
```

... and you'd be entirely correct! As a matter of fact, you can absolutely 100% pass exactly this function into `Store#select` and the `select()` operator:

{% stackblitz zack-simple-select file=src/app/app.state.ts%}

![mind blown](https://media2.giphy.com/media/ZF40pid2AozVC/giphy.gif)

[And if you REALLY want your mind blown, you can actually just swap out that `select` operator for `map` while you're at it]:

{% stackblitz zack-map-instead-of-select file=src/app/app.component.ts%}

If you're like me - you probably have gone for awhile in your NgRx journey before realizing this could work. All beginner material I've encountered on ngrx/store (including [the NgRx docs](https://ngrx.io/guide/store/selectors)) will tell you to write your selectors like this: 

```ts
import { createFeatureSelector, createSelector } from '@ngrx/store';

export interface FeatureState {
  array: string[];
}
export interface State {
  featureNameplace: FeatureState;
}

export selectFeatureNameplace = createFeatureSelector<FeatureState>('featureNameplace');
export selectArray = createSelector(
  selectFeatureNameplace,
  featureState => featureState.array
);
``` 

This gave me (and I suspect many folks) the impression that there's some deep magical-ness to selectors that when mixed with the equally magical `Store` object will magically create an `Observable`.

![it's magic baby](https://media.tenor.com/images/c3b381d75841684d76704841adbd2939/tenor.gif)

As it turns out, for the most part, these `createSelector` functions from the `@ngrx/store` API are just returning functions that return the same exact values as our original simple function.

[As a bonus! In case you weren't aware, selectors are SUPER easy to test because of this]:

```ts
import { selectArray, AppState } from './state.ts';

describe('selectArray', () => {
  test('returns the featureNameplace.array of a state object', () => {
    const state: AppState = {
      featureNameplace: {
        array: ['test'];
      }
    };
    const result = selectArray(state);
    expect(result).toEqual(['test']);
  });
});
```

There is still _some_ magic to the `createSelector()` function though. Here's the actual implementation of `createSelector()` [straight from the `@ngrx/store` source code](https://github.com/ngrx/platform/blob/10.1.1/modules/store/src/selector.ts#L555-L604):

```ts
export function createSelector(
  ...input: any[]
): MemoizedSelector<any, any> | MemoizedSelectorWithProps<any, any, any> {
  return createSelectorFactory(defaultMemoize)(...input);
}
```

As we can see, `createSelector` is actually just a wrapper for calling `createSelectorFactory()` with `defaultMemoize`, and then currying over the arguments originally passed into `createSelector()`.

![There is no fridge!!!](https://media4.giphy.com/media/ySpQaOYcOPMFVEUeOq/giphy.gif)

Note that this `createSelectorFactory()` function being called here is actually exported from `@ngrx/store` - meaning that it is actually meant for us to use! Let's take a look at [defaultMemoize](https://github.com/ngrx/platform/blob/10.1.1/modules/store/src/selector.ts#L67):

```ts
export function defaultMemoize(
  projectionFn: AnyFn,
  isArgumentsEqual = isEqualCheck,
  isResultEqual = isEqualCheck
): MemoizedProjection {
  let lastArguments: null | IArguments = null;
  // tslint:disable-next-line:no-any anything could be the result.
  let lastResult: any = null;
  let overrideResult: any;

  function reset() {
    lastArguments = null;
    lastResult = null;
  }

  function setResult(result: any = undefined) {
    overrideResult = { result };
  }

  function clearResult() {
    overrideResult = undefined;
  }

  // tslint:disable-next-line:no-any anything could be the result.
  function memoized(): any {
    if (overrideResult !== undefined) {
      return overrideResult.result;
    }

    if (!lastArguments) {
      lastResult = projectionFn.apply(null, arguments as any);
      lastArguments = arguments;
      return lastResult;
    }

    if (!isArgumentsChanged(arguments, lastArguments, isArgumentsEqual)) {
      return lastResult;
    }

    const newResult = projectionFn.apply(null, arguments as any);
    lastArguments = arguments;

    if (isResultEqual(lastResult, newResult)) {
      return lastResult;
    }

    lastResult = newResult;

    return newResult;
  }

  return { memoized, reset, setResult, clearResult };
}
```

Right off the bat, we can see that `defaultMemoize()` function is exported from `@ngrx/store` as well - we'll use this to our advantage later on!

When taking a deeper look into this function, we see that this is a [closure](https://medium.com/javascript-scene/master-the-javascript-interview-what-is-a-closure-b2f0d2152b36) that is exposing `memoize()`, `reset()`, `setResult()`, and `clearResult()` methods, with most of the action happening in `memoize()`. Essentially, this function will look at the closure's state and
+ if an `overrideResult` exists, it will return that result
+ if `lastArguments` don't exist, it will call the projection function (btw - a projection function is the function we pass as the last argument we pass to `createSelector()` when creating a selector the standard way), set the `lastResult` of the closure with the result of the call, and return that result.
+ if `lastArguments` exist and they match the current arguments (according to the `isArgumentsEqual` logic!) then return the `lastResult`
+ calculate a new result by calling the projection function, set `lastArguments` and `lastResult` (assuming it is different than your new result) and return either the `newResult` if new, or `lastResult` if they are equal.

![that's alot](https://media4.giphy.com/media/AS6bC3bKauQnwgyrad/200.gif)

TL;DR: if the relevant state is the same as last time the selector ran, the result is pulled from memory instead of called.

## Quick Aside on Projectors

It may not be entirely clear what a `projector` or `projectionFn` is, so to clear things up:

![Anatomy of a selector](https://i.imgur.com/ysuFuhb.png)

A projection function is the final argument of the selector factory.

Also note that mocking projection functions can be helpful for testing selectors:

```ts
describe('barAndFooSelector()', () => {
  test('mocking parent selectors', () => {
     const initialState = { bar: 'bar' };
     const initialFoo = 'foo';
     const result = barAndFooSelector.projector(
       initialState,
       initialFoo
     );
     expect(result).toEqual({ bar: 'bar', foo: 'foo' });
  });
});
```
(There may be some philosophical discussions regarding if this is testing an implementation detail [and I would tend to agree!!] but that's a story for another time!)

# Creating Our Own `createOrderDoesNotMatterSelector()`!!

Let's say that we'd like to make a selector for an array we're keeping in our global state, but the order of the array doesn't matter to us:

```ts
function removeMatch(
  arr: string[],
  target: string
): string[] {
  const matchIndex = arr.indexOf(target);
  return [
    ...arr.slice(0, matchIndex),
    ...arr.slice(matchIndex + 1)
  ];
}

function orderDoesNotMatterComparer(a: any, b: any): boolean {
  if (!Array.isArray(a) || !Array.isArray(b)) {
    return a === b;
  }
  if (a.length !== b.length) {
    return false;
  }
  let tempB = [...b];
  function reduceToDetermineIfArraysContainSameContents(
    previousCallResult: boolean,
    arrayMember: any
  ): boolean {
    if (previousCallResult === false) {
      return false;
    }
    if (tempB.includes(arrayMember)) {
      tempB = removeMatch(tempB, arrayMember);
      return true;
    }
    return false;
  }
  return a.reduce(
    reduceToDetermineIfArraysContainSameContents,
    true
  );
}
```

^ This function will tell us if two arrays are equal given that order doesn't count!

Once we have this function, we can pick up the `createSelectorFactory()` and the `defaultMemoize()` puzzle pieces that the `@ngrx/store` exposes and build our `createOrderDoesNotMatterSelector()`:

```ts
export const createOrderDoesNotMatterSelector =
  createSelectorFactory(
    (projectionFn) =>
      defaultMemoize(
        projectionFn,
        orderDoesNotMatterComparer,
        orderDoesNotMatterComparer
      )
  );
```

If using our new selector factory, we can optimize some array-like things! Let's make a new selector to select our array out of the `Store`, and also a selector from that to select the sum of all the items in that array. Here's an example app showing how our custom selector factory compares to the standard `createSelector()`.

{% stackblitz zack-create-order-does-not-matter-selector-factory %}

We can see when we click sort, we'll observe more emissions from our regular selectors than our 'order does not matter' selectors. Also, if we open the console, we'll see from the logs that even though the `regularSelectSum` is not emitting as much (there's a `distinctUntilChanged()` blocking the extra emissions), it is still calling the projector much more often than it's 'order does not matter' counterpart.

# Creating a Full History Cache Memoization Selector

Putting aside the `defaultMemoize()` function provided by `@ngrx/store`, we can actually write a memoization strategy that records all previous runs of a selector (recall that the `defaultMemoize()` will only remember the 1 previous result and arguments).

Before we get started, note that the `defaultMemoize()` strategy is there for a reason! Creating a full history cache will absolutely take up more memory, and for most selectors, it's probably not too often that duplicate arguments will be called except for back-to-back (&& the `defaultMemoize()` will cover back-to-back scenarios). So before you start recreating this across your codebases, make sure that the benefits of speed are worth the cost of extra memory (lulz, j/k, the only performance that matters is bundle size.... fml). A use-case where cache hits are likely and computation of the projector function is expensive would be the ideal for this kind of selector.

Also for more on memoization, be sure to [check out this article I wrote on the topic](https://blog.nrwl.io/computation-caching-the-fundamentals-behind-nxs-lightning-fast-execution-dc761fe41eb8)!

Alright, so essentially, we want to make a closure, the same way that the `defaultMemoize` function created a closure, but instead of tracking things like `lastResult` and `lastArguments`, we'll make a single cache object, that will serve as an indexed map of some representation of projector arguments to project results. Installing 'object-hash' from npm will get us standard SHA-1 hashing on the arguments, for indexing our cache, and from there, we'll check on the cache to see if a match exists. If it does, will return the match. If not, we'll call the projector, stash it in the cache, and then return it. All the other methods we can assign to no-ops too, since they're not needed in our solution.

```ts
import * as hash from 'object-hash';

const createFullHistorySelector = createSelectorFactory(
  (projectionFunction) => {
    const cache = {};

    function memoized() {
      const hashedArguments = hash(...arguments);
      if (cache[hashedArguments] != null) {
        cache[hashedArguments] = projectionFunction.apply(null, arguments);
        console.log('calculationMade');
      }
      return cache[hashedArguments];
    }
    return {
      memoized,
      reset: () => {},
      setResult: () => {},
      clearResult: () => {},
    };
  }
);
```

Now, we can proceed to recreate a similar example app comparing how our new selector factory fairs vs. the default one:

{% stackblitz zack-create-full-history-selector-factory %}

Be sure to open the console on this one, and we'll see if we push on 1, then 5, then 3 - each of these three with result in a cache miss, causing the projection function to be run.

Then if we pop all three off, we'll see the cache hits for these, AND our full history projectors will not get called! (Meanwhile the regular selectors are having to re-call their projectors!).

Going even further though! If we push back on 1, then 5, then 3, we'll continue to see cache hits and no calls to the  projectors!

Very cool! What's more - this selector factory could absolutely be exported from a utility library and used widely across just about any selector!!

# Creating a `createImmutableSelector()` function!

In my earlier days of working with NgRx, I had assumed that the observables created by selectors were immutable - that a deep clone of them were being emitted from the observable, and that they were not being passed by reference.

I WAS WRONG.

![what a fool I was](https://media1.tenor.com/images/6eef83caee3aa6b2517e10abb711144f/tenor.gif?itemid=5349518)

My impression is that many people make similar assumptions about `select`! But with our new-found knowledge of `createSelectorFactory()`, we can fix this for everyone!

{% stackblitz zack-create-immutable-selector-factory %}

Note that I'm still using full history strategy (as well as immutable returns) in this example. It's essentially a copy & paste of the previous example, but with the `cloneDeep()` (from Lodash) called just before returning!

Looking at the component, we can see for the immutable selector, we are making a local copy that we can freely change around - as if it's in its own scope (because it is 🤯) - without altering the global state! This can be very useful in some situations/use-cases!

On the other hand, trying to pop off of the regular selector will produce an error. This is actually quite beneficial as the alternative would have been changing the value of the `Store` OUTSIDE of the reducers!!

Like the `createFullHistorySelector()` factory we made in the previous section, this one too is very generic in terms of being able to be used in virtually any selector that you would have made with `createSelector()`!

# CONCLUSION

Hopefully there's been lots of interesting learning about selectors for you in this article!! I hope that it's given you a better understanding of how NgRx is put together, as well as maybe some ideas on how to create your own selector factories - or even just use some of the factories provided in this article!

Huge props to the NgRx team - the craftsmanship of this lib really holds up when taking a deeper look into it, and I think that speaks very highly of everyone on the core team!!

# More Content By Zack

[Blogs](https://zackderose.dev/blogs)
[YouTube](https://www.youtube.com/channel/UCIEY8ETWdW3EX-3nWE-T0dQ)
[Twitch](https://www.twitch.tv/zackderose)
[Twitter](https://twitter.com/zackderose)
[All Video Content Combined](https://zackderose.dev/videos)