ngrxangular-ngrx-datangrx-selectors

NGRX selectors: factory selector within another selector without prop in createSelector method


Using the factory selector pattern const selectA = (id: number) => createSelector(...) I have an instance where I want to reuse this selector within another selector (that iterates through an array of IDs) but I don't know the value to pass into the factor selector when calling createSelector.

So I have a selector that I use whenever I want to get a slice of the state for component A.

const selectA = (id: number) =>
    createSelector(
        selector1.selectEntityMap,
        selector2.selectEntityMap,
        selector3ById(id), 
        (
        thing1, 
        thing2, 
        thing3
        ) => {
            return ...
       });

Now I want to get a list of component A for each item in an array.

const selectListOfA = (ids: number[]) =>
    createSelector(
    selectA, 
    (selectorA) => {
        return ids.map((id) => selectorA(id));
    });

The problem is selectA, which is now a factory selector, expects a parameter, but I don't know it when calling createSelector.

I can get the code to compile by creating another factory onto of the factory const selectAFactory = () => selectA;

And then reference the new factory in the createSelector

const selectListOfA = (ids: number[]) =>
    createSelector(
    selectAFactory, <<< here 
    (selectorA) => {
        return ids.map((id) => selectorA(id));
    });

But of course, what's now happening is the selector is returning a list of MemoizedSelector[].

This pattern doesn't seem like it should be this complicated, are people not reusing their selectors in this way, what am I missing?


Solution

  • The function returned by selectA is a standard function, ie nothing magical about it, as explained well in this blog post: https://dev.to/zackderose/ngrx-fun-with-createselectorfactory-hng

    This means selectListOfA can simply call the function returned from selectA for each id and an array of the state slices for component A will be returned:

    export const selectListOfA = (ids: number[]) =>
      createSelector(
        (state) => state,
        (state) => ids.map((id) => selectA(id)(state))
      );
    

    This works as expected but since the projector function will be executed every time anything in the store changes (recreating the selector for every id) this solution will have major performance issues.

    We could just as well simplify the code to this with equally poor performance:

    const selectListOfA = (ids: number[]) =>
      (state) => ids.map(
        (id: number) => selectA(id)(state)
      );
    

    If we instead supply an array of selectors as input to the createSelector call then Ngrx will be able to correctly determine when it has to reevaluate the selectA selectors:

    const selectListOfA = (ids: number[]) =>
      createSelector(
        ids.map((id) => selectA(id)), // This results in an array of selectors
        (...resultArr) => resultArr
      );
    

    However, Typescript will complain since the createSelector method does not have a matching overload declared for an array of variable length so we need to loosen up the input type of the array (to any) as well as specify the return type of selectListOfA.

    The answer to the question is thus:

      const selectListOfA = (ids: number[]) =>
      (createSelector(
        ids.map((id) => selectA(id)) as any,
        (...resultArr) => resultArr
      ) as (state) => string[]);