Pass the argument index to the comparator function
Which @ngrx/* package(s) are relevant/related to the feature request?
store
Information
I'm creating a custom createSelector function by using the createSelectorFactory but my custom logic requires me to know the index of the argument that I'm comparing. Is it possible to pass the index at this line?
https://github.com/ngrx/platform/blob/master/modules/store/src/selector.ts#L56
The type of argument comparator would be:
export type ComparatorFn = (a: any, b: any, index: number) => boolean;
Describe any alternatives/workarounds you're currently using
No response
I would be willing to submit a PR to fix this issue
- [X] Yes
- [ ] No
Could you elaborate/give an example why the index is needed?
@timdeschryver this is not 100% useful, it was just for testing!
import { createSelector, createSelectorFactory, defaultMemoize } from '@ngrx/store';
import { equals } from 'ramda';
/** A custom selector that creates a proxy for every object in params and detects changes for the visited keys. */
export const createSmartSelector = createSelectorFactory(projectorFn => {
const keyMap: Record<number, Set<string | symbol>> = {};
const fn = (...args: any[]) => {
args = args.map((arg, index) => {
keyMap[index] = new Set();
if (!arg || typeof arg !== 'object') {
return arg;
}
return new Proxy(arg, {
get: (target, prop) => {
keyMap[index].add(prop);
return target[prop];
},
getPrototypeOf: target => target,
});
});
return projectorFn(...args);
};
const compareFn = (a: any, b: any) => {
const index = 0; // To make it work, we should receive index from params here
const keys = keyMap[index];
if (!keys || keys.size === 0) {
return equals(a, b);
}
return Array.from(keys).every(key => equals(a[key], b[key]));
};
return defaultMemoize(fn, compareFn);
}) as typeof createSelector;
/** Returns the original object from proxy. */
export const getOriginalObject = <T>(proxy: T): T => {
return Object.getPrototypeOf(proxy);
};
@timdeschryver here is another good example that I found lately. I wanted to only check equality for the slateId selector and without an index, I can't tell which prop I'm talking about. A workaround is to check if a or b is string:
const selectedSlateSelector = createSelectorFactory(projectorFn =>
defaultMemoize(projectorFn, (a, b) => {
if (typeof a === 'string' || typeof b === 'string') {
return a === b;
}
return true;
}, (a, b) => R.equals(a, b))
) as typeof createSelector;
export const selectedSlate = selectedSlateSelector(
slateEntities,
slateId,
(_slateEntities, _slateId) => _slateEntities[_slateId] ?? null
);
But if the input selectors had the same type, I wouldn't be able to do it, unless I had the index of the argument.