Skip to content

Commit

Permalink
feat(Store): createSelector with an array of selectors (#340)
Browse files Browse the repository at this point in the history
Closes #192
  • Loading branch information
timdeschryver authored and MikeRyanDev committed Sep 2, 2017
1 parent a82f675 commit 2f6a035
Show file tree
Hide file tree
Showing 2 changed files with 169 additions and 1 deletion.
86 changes: 86 additions & 0 deletions modules/store/spec/selector.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,92 @@ describe('Selectors', () => {
});
});

describe('createSelector with arrays', () => {
it('should deliver the value of selectors to the projection function', () => {
const projectFn = jasmine.createSpy('projectionFn');
const selector = createSelector([incrementOne, incrementTwo], projectFn)(
{}
);

expect(projectFn).toHaveBeenCalledWith(countOne, countTwo);
});

it('should be possible to test a projector fn independent from the selectors it is composed of', () => {
const projectFn = jasmine.createSpy('projectionFn');
const selector = createSelector([incrementOne, incrementTwo], projectFn);

selector.projector('', '');

expect(incrementOne).not.toHaveBeenCalled();
expect(incrementTwo).not.toHaveBeenCalled();
expect(projectFn).toHaveBeenCalledWith('', '');
});

it('should call the projector function only when the value of a dependent selector change', () => {
const firstState = { first: 'state', unchanged: 'state' };
const secondState = { second: 'state', unchanged: 'state' };
const neverChangingSelector = jasmine
.createSpy('unchangedSelector')
.and.callFake((state: any) => {
return state.unchanged;
});
const projectFn = jasmine.createSpy('projectionFn');
const selector = createSelector([neverChangingSelector], projectFn);

selector(firstState);
selector(secondState);

expect(projectFn).toHaveBeenCalledTimes(1);
});

it('should memoize the function', () => {
const firstState = { first: 'state' };
const secondState = { second: 'state' };
const projectFn = jasmine.createSpy('projectionFn');
const selector = createSelector(
[incrementOne, incrementTwo, incrementThree],
projectFn
);

selector(firstState);
selector(firstState);
selector(firstState);
selector(secondState);

expect(incrementOne).toHaveBeenCalledTimes(2);
expect(incrementTwo).toHaveBeenCalledTimes(2);
expect(incrementThree).toHaveBeenCalledTimes(2);
expect(projectFn).toHaveBeenCalledTimes(2);
});

it('should allow you to release memoized arguments', () => {
const state = { first: 'state' };
const projectFn = jasmine.createSpy('projectionFn');
const selector = createSelector([incrementOne], projectFn);

selector(state);
selector(state);
selector.release();
selector(state);
selector(state);

expect(projectFn).toHaveBeenCalledTimes(2);
});

it('should recursively release ancestor selectors', () => {
const grandparent = createSelector([incrementOne], a => a);
const parent = createSelector([grandparent], a => a);
const child = createSelector([parent], a => a);
spyOn(grandparent, 'release').and.callThrough();
spyOn(parent, 'release').and.callThrough();

child.release();

expect(grandparent.release).toHaveBeenCalled();
expect(parent.release).toHaveBeenCalled();
});
});

describe('createFeatureSelector', () => {
let featureName = '@ngrx/router-store';
let featureSelector: (state: any) => number;
Expand Down
84 changes: 83 additions & 1 deletion modules/store/src/selector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,24 +43,45 @@ export function createSelector<State, S1, Result>(
s1: Selector<State, S1>,
projector: (S1: S1) => Result
): MemoizedSelector<State, Result>;
export function createSelector<State, S1, Result>(
selectors: [Selector<State, S1>],
projector: (s1: S1) => Result
): MemoizedSelector<State, Result>;
export function createSelector<State, S1, S2, Result>(
s1: Selector<State, S1>,
s2: Selector<State, S2>,
projector: (s1: S1, s2: S2) => Result
): MemoizedSelector<State, Result>;
export function createSelector<State, S1, S2, Result>(
selectors: [Selector<State, S1>, Selector<State, S2>],
projector: (s1: S1, s2: S2) => Result
): MemoizedSelector<State, Result>;
export function createSelector<State, S1, S2, S3, Result>(
s1: Selector<State, S1>,
s2: Selector<State, S2>,
s3: Selector<State, S3>,
projector: (s1: S1, s2: S2, s3: S3) => Result
): MemoizedSelector<State, Result>;
export function createSelector<State, S1, S2, S3, Result>(
selectors: [Selector<State, S1>, Selector<State, S2>, Selector<State, S3>],
projector: (s1: S1, s2: S2, s3: S3) => Result
): MemoizedSelector<State, Result>;
export function createSelector<State, S1, S2, S3, S4, Result>(
s1: Selector<State, S1>,
s2: Selector<State, S2>,
s3: Selector<State, S3>,
s4: Selector<State, S4>,
projector: (s1: S1, s2: S2, s3: S3, s4: S4) => Result
): MemoizedSelector<State, Result>;
export function createSelector<State, S1, S2, S3, S4, Result>(
selectors: [
Selector<State, S1>,
Selector<State, S2>,
Selector<State, S3>,
Selector<State, S4>
],
projector: (s1: S1, s2: S2, s3: S3, s4: S4) => Result
): MemoizedSelector<State, Result>;
export function createSelector<State, S1, S2, S3, S4, S5, Result>(
s1: Selector<State, S1>,
s2: Selector<State, S2>,
Expand All @@ -69,6 +90,16 @@ export function createSelector<State, S1, S2, S3, S4, S5, Result>(
s5: Selector<State, S5>,
projector: (s1: S1, s2: S2, s3: S3, s4: S4, s5: S5) => Result
): MemoizedSelector<State, Result>;
export function createSelector<State, S1, S2, S3, S4, S5, Result>(
selectors: [
Selector<State, S1>,
Selector<State, S2>,
Selector<State, S3>,
Selector<State, S4>,
Selector<State, S5>
],
projector: (s1: S1, s2: S2, s3: S3, s4: S4, s5: S5) => Result
): MemoizedSelector<State, Result>;
export function createSelector<State, S1, S2, S3, S4, S5, S6, Result>(
s1: Selector<State, S1>,
s2: Selector<State, S2>,
Expand All @@ -78,6 +109,17 @@ export function createSelector<State, S1, S2, S3, S4, S5, S6, Result>(
s6: Selector<State, S6>,
projector: (s1: S1, s2: S2, s3: S3, s4: S4, s5: S5, s6: S6) => Result
): MemoizedSelector<State, Result>;
export function createSelector<State, S1, S2, S3, S4, S5, S6, Result>(
selectors: [
Selector<State, S1>,
Selector<State, S2>,
Selector<State, S3>,
Selector<State, S4>,
Selector<State, S5>,
Selector<State, S6>
],
projector: (s1: S1, s2: S2, s3: S3, s4: S4, s5: S5, s6: S6) => Result
): MemoizedSelector<State, Result>;
export function createSelector<State, S1, S2, S3, S4, S5, S6, S7, Result>(
s1: Selector<State, S1>,
s2: Selector<State, S2>,
Expand All @@ -88,6 +130,18 @@ export function createSelector<State, S1, S2, S3, S4, S5, S6, S7, Result>(
s7: Selector<State, S7>,
projector: (s1: S1, s2: S2, s3: S3, s4: S4, s5: S5, s6: S6, s7: S7) => Result
): MemoizedSelector<State, Result>;
export function createSelector<State, S1, S2, S3, S4, S5, S6, S7, Result>(
selectors: [
Selector<State, S1>,
Selector<State, S2>,
Selector<State, S3>,
Selector<State, S4>,
Selector<State, S5>,
Selector<State, S6>,
Selector<State, S7>
],
projector: (s1: S1, s2: S2, s3: S3, s4: S4, s5: S5, s6: S6, s7: S7) => Result
): MemoizedSelector<State, Result>;
export function createSelector<State, S1, S2, S3, S4, S5, S6, S7, S8, Result>(
s1: Selector<State, S1>,
s2: Selector<State, S2>,
Expand All @@ -108,7 +162,35 @@ export function createSelector<State, S1, S2, S3, S4, S5, S6, S7, S8, Result>(
s8: S8
) => Result
): MemoizedSelector<State, Result>;
export function createSelector(...args: any[]): Selector<any, any> {
export function createSelector<State, S1, S2, S3, S4, S5, S6, S7, S8, Result>(
selectors: [
Selector<State, S1>,
Selector<State, S2>,
Selector<State, S3>,
Selector<State, S4>,
Selector<State, S5>,
Selector<State, S6>,
Selector<State, S7>,
Selector<State, S8>
],
projector: (
s1: S1,
s2: S2,
s3: S3,
s4: S4,
s5: S5,
s6: S6,
s7: S7,
s8: S8
) => Result
): MemoizedSelector<State, Result>;
export function createSelector(...input: any[]): Selector<any, any> {
let args = input;
if (Array.isArray(args[0])) {
const [head, ...tail] = args;
args = [...head, ...tail];
}

const selectors = args.slice(0, args.length - 1);
const projector = args[args.length - 1];
const memoizedSelectors = selectors.filter(
Expand Down

0 comments on commit 2f6a035

Please sign in to comment.