Skip to content

Commit 2f6a035

Browse files
timdeschryverMikeRyanDev
authored andcommitted
feat(Store): createSelector with an array of selectors (#340)
Closes #192
1 parent a82f675 commit 2f6a035

File tree

2 files changed

+169
-1
lines changed

2 files changed

+169
-1
lines changed

modules/store/spec/selector.spec.ts

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,92 @@ describe('Selectors', () => {
119119
});
120120
});
121121

122+
describe('createSelector with arrays', () => {
123+
it('should deliver the value of selectors to the projection function', () => {
124+
const projectFn = jasmine.createSpy('projectionFn');
125+
const selector = createSelector([incrementOne, incrementTwo], projectFn)(
126+
{}
127+
);
128+
129+
expect(projectFn).toHaveBeenCalledWith(countOne, countTwo);
130+
});
131+
132+
it('should be possible to test a projector fn independent from the selectors it is composed of', () => {
133+
const projectFn = jasmine.createSpy('projectionFn');
134+
const selector = createSelector([incrementOne, incrementTwo], projectFn);
135+
136+
selector.projector('', '');
137+
138+
expect(incrementOne).not.toHaveBeenCalled();
139+
expect(incrementTwo).not.toHaveBeenCalled();
140+
expect(projectFn).toHaveBeenCalledWith('', '');
141+
});
142+
143+
it('should call the projector function only when the value of a dependent selector change', () => {
144+
const firstState = { first: 'state', unchanged: 'state' };
145+
const secondState = { second: 'state', unchanged: 'state' };
146+
const neverChangingSelector = jasmine
147+
.createSpy('unchangedSelector')
148+
.and.callFake((state: any) => {
149+
return state.unchanged;
150+
});
151+
const projectFn = jasmine.createSpy('projectionFn');
152+
const selector = createSelector([neverChangingSelector], projectFn);
153+
154+
selector(firstState);
155+
selector(secondState);
156+
157+
expect(projectFn).toHaveBeenCalledTimes(1);
158+
});
159+
160+
it('should memoize the function', () => {
161+
const firstState = { first: 'state' };
162+
const secondState = { second: 'state' };
163+
const projectFn = jasmine.createSpy('projectionFn');
164+
const selector = createSelector(
165+
[incrementOne, incrementTwo, incrementThree],
166+
projectFn
167+
);
168+
169+
selector(firstState);
170+
selector(firstState);
171+
selector(firstState);
172+
selector(secondState);
173+
174+
expect(incrementOne).toHaveBeenCalledTimes(2);
175+
expect(incrementTwo).toHaveBeenCalledTimes(2);
176+
expect(incrementThree).toHaveBeenCalledTimes(2);
177+
expect(projectFn).toHaveBeenCalledTimes(2);
178+
});
179+
180+
it('should allow you to release memoized arguments', () => {
181+
const state = { first: 'state' };
182+
const projectFn = jasmine.createSpy('projectionFn');
183+
const selector = createSelector([incrementOne], projectFn);
184+
185+
selector(state);
186+
selector(state);
187+
selector.release();
188+
selector(state);
189+
selector(state);
190+
191+
expect(projectFn).toHaveBeenCalledTimes(2);
192+
});
193+
194+
it('should recursively release ancestor selectors', () => {
195+
const grandparent = createSelector([incrementOne], a => a);
196+
const parent = createSelector([grandparent], a => a);
197+
const child = createSelector([parent], a => a);
198+
spyOn(grandparent, 'release').and.callThrough();
199+
spyOn(parent, 'release').and.callThrough();
200+
201+
child.release();
202+
203+
expect(grandparent.release).toHaveBeenCalled();
204+
expect(parent.release).toHaveBeenCalled();
205+
});
206+
});
207+
122208
describe('createFeatureSelector', () => {
123209
let featureName = '@ngrx/router-store';
124210
let featureSelector: (state: any) => number;

modules/store/src/selector.ts

Lines changed: 83 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,24 +43,45 @@ export function createSelector<State, S1, Result>(
4343
s1: Selector<State, S1>,
4444
projector: (S1: S1) => Result
4545
): MemoizedSelector<State, Result>;
46+
export function createSelector<State, S1, Result>(
47+
selectors: [Selector<State, S1>],
48+
projector: (s1: S1) => Result
49+
): MemoizedSelector<State, Result>;
4650
export function createSelector<State, S1, S2, Result>(
4751
s1: Selector<State, S1>,
4852
s2: Selector<State, S2>,
4953
projector: (s1: S1, s2: S2) => Result
5054
): MemoizedSelector<State, Result>;
55+
export function createSelector<State, S1, S2, Result>(
56+
selectors: [Selector<State, S1>, Selector<State, S2>],
57+
projector: (s1: S1, s2: S2) => Result
58+
): MemoizedSelector<State, Result>;
5159
export function createSelector<State, S1, S2, S3, Result>(
5260
s1: Selector<State, S1>,
5361
s2: Selector<State, S2>,
5462
s3: Selector<State, S3>,
5563
projector: (s1: S1, s2: S2, s3: S3) => Result
5664
): MemoizedSelector<State, Result>;
65+
export function createSelector<State, S1, S2, S3, Result>(
66+
selectors: [Selector<State, S1>, Selector<State, S2>, Selector<State, S3>],
67+
projector: (s1: S1, s2: S2, s3: S3) => Result
68+
): MemoizedSelector<State, Result>;
5769
export function createSelector<State, S1, S2, S3, S4, Result>(
5870
s1: Selector<State, S1>,
5971
s2: Selector<State, S2>,
6072
s3: Selector<State, S3>,
6173
s4: Selector<State, S4>,
6274
projector: (s1: S1, s2: S2, s3: S3, s4: S4) => Result
6375
): MemoizedSelector<State, Result>;
76+
export function createSelector<State, S1, S2, S3, S4, Result>(
77+
selectors: [
78+
Selector<State, S1>,
79+
Selector<State, S2>,
80+
Selector<State, S3>,
81+
Selector<State, S4>
82+
],
83+
projector: (s1: S1, s2: S2, s3: S3, s4: S4) => Result
84+
): MemoizedSelector<State, Result>;
6485
export function createSelector<State, S1, S2, S3, S4, S5, Result>(
6586
s1: Selector<State, S1>,
6687
s2: Selector<State, S2>,
@@ -69,6 +90,16 @@ export function createSelector<State, S1, S2, S3, S4, S5, Result>(
6990
s5: Selector<State, S5>,
7091
projector: (s1: S1, s2: S2, s3: S3, s4: S4, s5: S5) => Result
7192
): MemoizedSelector<State, Result>;
93+
export function createSelector<State, S1, S2, S3, S4, S5, Result>(
94+
selectors: [
95+
Selector<State, S1>,
96+
Selector<State, S2>,
97+
Selector<State, S3>,
98+
Selector<State, S4>,
99+
Selector<State, S5>
100+
],
101+
projector: (s1: S1, s2: S2, s3: S3, s4: S4, s5: S5) => Result
102+
): MemoizedSelector<State, Result>;
72103
export function createSelector<State, S1, S2, S3, S4, S5, S6, Result>(
73104
s1: Selector<State, S1>,
74105
s2: Selector<State, S2>,
@@ -78,6 +109,17 @@ export function createSelector<State, S1, S2, S3, S4, S5, S6, Result>(
78109
s6: Selector<State, S6>,
79110
projector: (s1: S1, s2: S2, s3: S3, s4: S4, s5: S5, s6: S6) => Result
80111
): MemoizedSelector<State, Result>;
112+
export function createSelector<State, S1, S2, S3, S4, S5, S6, Result>(
113+
selectors: [
114+
Selector<State, S1>,
115+
Selector<State, S2>,
116+
Selector<State, S3>,
117+
Selector<State, S4>,
118+
Selector<State, S5>,
119+
Selector<State, S6>
120+
],
121+
projector: (s1: S1, s2: S2, s3: S3, s4: S4, s5: S5, s6: S6) => Result
122+
): MemoizedSelector<State, Result>;
81123
export function createSelector<State, S1, S2, S3, S4, S5, S6, S7, Result>(
82124
s1: Selector<State, S1>,
83125
s2: Selector<State, S2>,
@@ -88,6 +130,18 @@ export function createSelector<State, S1, S2, S3, S4, S5, S6, S7, Result>(
88130
s7: Selector<State, S7>,
89131
projector: (s1: S1, s2: S2, s3: S3, s4: S4, s5: S5, s6: S6, s7: S7) => Result
90132
): MemoizedSelector<State, Result>;
133+
export function createSelector<State, S1, S2, S3, S4, S5, S6, S7, Result>(
134+
selectors: [
135+
Selector<State, S1>,
136+
Selector<State, S2>,
137+
Selector<State, S3>,
138+
Selector<State, S4>,
139+
Selector<State, S5>,
140+
Selector<State, S6>,
141+
Selector<State, S7>
142+
],
143+
projector: (s1: S1, s2: S2, s3: S3, s4: S4, s5: S5, s6: S6, s7: S7) => Result
144+
): MemoizedSelector<State, Result>;
91145
export function createSelector<State, S1, S2, S3, S4, S5, S6, S7, S8, Result>(
92146
s1: Selector<State, S1>,
93147
s2: Selector<State, S2>,
@@ -108,7 +162,35 @@ export function createSelector<State, S1, S2, S3, S4, S5, S6, S7, S8, Result>(
108162
s8: S8
109163
) => Result
110164
): MemoizedSelector<State, Result>;
111-
export function createSelector(...args: any[]): Selector<any, any> {
165+
export function createSelector<State, S1, S2, S3, S4, S5, S6, S7, S8, Result>(
166+
selectors: [
167+
Selector<State, S1>,
168+
Selector<State, S2>,
169+
Selector<State, S3>,
170+
Selector<State, S4>,
171+
Selector<State, S5>,
172+
Selector<State, S6>,
173+
Selector<State, S7>,
174+
Selector<State, S8>
175+
],
176+
projector: (
177+
s1: S1,
178+
s2: S2,
179+
s3: S3,
180+
s4: S4,
181+
s5: S5,
182+
s6: S6,
183+
s7: S7,
184+
s8: S8
185+
) => Result
186+
): MemoizedSelector<State, Result>;
187+
export function createSelector(...input: any[]): Selector<any, any> {
188+
let args = input;
189+
if (Array.isArray(args[0])) {
190+
const [head, ...tail] = args;
191+
args = [...head, ...tail];
192+
}
193+
112194
const selectors = args.slice(0, args.length - 1);
113195
const projector = args[args.length - 1];
114196
const memoizedSelectors = selectors.filter(

0 commit comments

Comments
 (0)