From 35a484872afd8ec1ca5d14c2019d06dd7976fd6b Mon Sep 17 00:00:00 2001 From: Tim Deschryver <28659384+timdeschryver@users.noreply.github.com> Date: Fri, 27 Jul 2018 12:27:06 +0200 Subject: [PATCH] feat(Store): createSelector with only a props selector --- modules/store/spec/integration.spec.ts | 32 ++++++++++++++++++ modules/store/spec/selector.spec.ts | 45 ++++++++++++++++++++++++++ modules/store/src/selector.ts | 10 ++++++ 3 files changed, 87 insertions(+) diff --git a/modules/store/spec/integration.spec.ts b/modules/store/spec/integration.spec.ts index f7db476a6b..f9998590e0 100644 --- a/modules/store/spec/integration.spec.ts +++ b/modules/store/spec/integration.spec.ts @@ -177,6 +177,38 @@ describe('ngRx Integration spec', () => { }); it('should use props to get a todo', () => { + const getTodosById = createSelector( + (state: TodoAppSchema, id: number) => { + return state.todos.find(p => p.id === id); + } + ); + + let testCase = 1; + const todo$ = store.pipe(select(getTodosById, 2)); + todo$.subscribe(todo => { + if (testCase === 1) { + expect(todo).toEqual(undefined); + } else if (testCase === 2) { + expect(todo).toEqual({ + id: 2, + text: 'second todo', + completed: false, + }); + } else if (testCase === 3) { + expect(todo).toEqual({ id: 2, text: 'second todo', completed: true }); + } + testCase++; + }); + + store.dispatch({ type: ADD_TODO, payload: { text: 'first todo' } }); + store.dispatch({ type: ADD_TODO, payload: { text: 'second todo' } }); + store.dispatch({ + type: COMPLETE_TODO, + payload: { id: 2 }, + }); + }); + + it('should use the selector and props to get a todo', () => { const getTodosState = createFeatureSelector( 'todos' ); diff --git a/modules/store/spec/selector.spec.ts b/modules/store/spec/selector.spec.ts index 5899581496..b6f62e8ea2 100644 --- a/modules/store/spec/selector.spec.ts +++ b/modules/store/spec/selector.spec.ts @@ -224,6 +224,51 @@ describe('Selectors', () => { }); }); + describe('createSelector with only a props selector', () => { + it('should deliver the state and the props to the projection function', () => { + const projectFn = jasmine.createSpy('projectionFn'); + const state = { counter: {} }; + const props = { value: 47 }; + const selector = createSelector(projectFn)(state, props); + expect(projectFn).toHaveBeenCalledWith(state, props); + }); + + it('should be possible to use a projector fn', () => { + const projectFn = jasmine.createSpy('projectionFn'); + const selector = createSelector(projectFn); + selector.projector('foo', 'bar'); + expect(projectFn).toHaveBeenCalledWith('foo', 'bar'); + }); + + it('should call the projector function when the state changes', () => { + const projectFn = jasmine.createSpy('projectionFn'); + const selector = createSelector(projectFn); + + const firstState = { first: 'state' }; + const secondState = { second: 'state' }; + const props = { foo: 'props' }; + selector(firstState, props); + selector(firstState, props); + selector(secondState, props); + expect(projectFn).toHaveBeenCalledTimes(2); + }); + + it('should allow you to release memoized arguments', () => { + const state = { first: 'state' }; + const props = { first: 'props' }; + const projectFn = jasmine.createSpy('projectionFn'); + const selector = createSelector(projectFn); + + selector(state, props); + selector(state, props); + selector.release(); + selector(state, props); + selector(state, props); + + expect(projectFn).toHaveBeenCalledTimes(2); + }); + }); + describe('createSelector with arrays', () => { it('should deliver the value of selectors to the projection function', () => { const projectFn = jasmine.createSpy('projectionFn'); diff --git a/modules/store/src/selector.ts b/modules/store/src/selector.ts index f4e0512f2e..1f42136531 100644 --- a/modules/store/src/selector.ts +++ b/modules/store/src/selector.ts @@ -435,6 +435,10 @@ export function createSelector< ) => Result ): MemoizedSelectorWithProps; +export function createSelector( + projector: SelectorWithProps +): MemoizedSelectorWithProps; + export function createSelector( ...input: any[] ): Selector | SelectorWithProps { @@ -501,6 +505,12 @@ export function createSelectorFactory( }); const memoizedState = defaultMemoize(function(state: any, props: any) { + // createSelector works directly on state + // e.g. createSelector((state, props) => ...) + if (selectors.length === 0) { + return projector.apply(null, [state, props]); + } + return options.stateFn.apply(null, [ state, selectors,