Skip to content

Commit 77eed24

Browse files
brandonrobertsMikeRyanDev
authored andcommitted
feat(Store): Add lettable select operator
Introduces lettable operator for Store.select. BREAKING CHANGE: Updates minimum version of RxJS dependency. BEFORE: Minimum peer dependency of RxJS ^5.0.0 AFTER: Minimum peer dependency of RxJS ^5.5.0
1 parent d5e1814 commit 77eed24

File tree

7 files changed

+107
-33
lines changed

7 files changed

+107
-33
lines changed

modules/router-store/src/router_store_module.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import {
1111
RouterStateSnapshot,
1212
RoutesRecognized,
1313
} from '@angular/router';
14-
import { Store } from '@ngrx/store';
14+
import { Store, select } from '@ngrx/store';
1515
import { of } from 'rxjs/observable/of';
1616
import {
1717
DefaultRouterStateSerializer,
@@ -255,7 +255,7 @@ export class StoreRouterConnectingModule {
255255
this.store.subscribe(s => {
256256
this.storeState = s;
257257
});
258-
this.store.select(this.stateKey).subscribe(() => {
258+
this.store.pipe(select(this.stateKey)).subscribe(() => {
259259
this.navigateIfNeeded();
260260
});
261261
}

modules/store/spec/edge.spec.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { Observable } from 'rxjs/Observable';
22
import { todos, todoCount } from './fixtures/edge_todos';
33
import { createInjector } from './helpers/injector';
4-
import { Store, StoreModule } from '../';
4+
import { Store, StoreModule, select } from '../';
55

66
interface TestAppSchema {
77
counter1: number;
@@ -36,12 +36,12 @@ describe('ngRx Store', () => {
3636
let todosNextCount = 0;
3737
let todosCountNextCount = 0;
3838

39-
store.select('todos').subscribe((todos: any[]) => {
39+
store.pipe(select('todos')).subscribe((todos: any[]) => {
4040
todosNextCount++;
4141
store.dispatch({ type: 'SET_COUNT', payload: todos.length });
4242
});
4343

44-
store.select('todoCount').subscribe(count => {
44+
store.pipe(select('todoCount')).subscribe(count => {
4545
todosCountNextCount++;
4646
});
4747

modules/store/spec/integration.spec.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
combineReducers,
1010
ActionReducer,
1111
ActionReducerMap,
12+
select,
1213
} from '../';
1314
import { ReducerManager, INITIAL_STATE, State } from '../src/private_export';
1415
import {
@@ -142,8 +143,8 @@ describe('ngRx Integration spec', () => {
142143
let currentlyVisibleTodos: Todo[] = [];
143144

144145
Observable.combineLatest(
145-
store.select('visibilityFilter'),
146-
store.select('todos'),
146+
store.pipe(select('visibilityFilter')),
147+
store.pipe(select('todos')),
147148
filterVisibleTodos
148149
).subscribe(visibleTodos => {
149150
currentlyVisibleTodos = visibleTodos;
@@ -221,7 +222,7 @@ describe('ngRx Integration spec', () => {
221222
},
222223
];
223224

224-
store.select(state => state).subscribe(state => {
225+
store.pipe(select(state => state)).subscribe(state => {
225226
expect(state).toEqual(expected.shift());
226227
});
227228
});

modules/store/spec/ngc/main.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { NgModule, Component, InjectionToken } from '@angular/core';
22
import { platformDynamicServer } from '@angular/platform-server';
33
import { BrowserModule } from '@angular/platform-browser';
4-
import { Store, StoreModule, combineReducers } from '../../';
4+
import { Store, StoreModule, combineReducers, select } from '../../';
55
import { counterReducer, INCREMENT, DECREMENT } from '../fixtures/counter';
66
import { todos } from '../fixtures/todos';
77
import { Observable } from 'rxjs/Observable';
@@ -40,7 +40,7 @@ export const reducerToken = new InjectionToken('Reducers');
4040
export class NgcSpecComponent {
4141
count: Observable<number>;
4242
constructor(public store: Store<AppState>) {
43-
this.count = store.select(state => state.count);
43+
this.count = store.pipe(select(state => state.count));
4444
}
4545
increment() {
4646
this.store.dispatch({ type: INCREMENT });

modules/store/spec/store.spec.ts

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,13 @@ import 'rxjs/add/operator/take';
22
import { ReflectiveInjector } from '@angular/core';
33
import { hot } from 'jasmine-marbles';
44
import { createInjector } from './helpers/injector';
5-
import { ActionsSubject, ReducerManager, Store, StoreModule } from '../';
5+
import {
6+
ActionsSubject,
7+
ReducerManager,
8+
Store,
9+
StoreModule,
10+
select,
11+
} from '../';
612
import {
713
counterReducer,
814
INCREMENT,
@@ -83,7 +89,7 @@ describe('ngRx Store', () => {
8389

8490
counterSteps.subscribe(action => store.dispatch(action));
8591

86-
const counterStateWithString = store.select('counter1');
92+
const counterStateWithString = store.pipe(select('counter1'));
8793

8894
const stateSequence = 'i-v--w--x--y--z';
8995
const counter1Values = { i: 0, v: 1, w: 2, x: 1, y: 0, z: 1 };
@@ -98,7 +104,7 @@ describe('ngRx Store', () => {
98104

99105
counterSteps.subscribe(action => store.dispatch(action));
100106

101-
const counterStateWithFunc = store.select(s => s.counter1);
107+
const counterStateWithFunc = store.pipe(select(s => s.counter1));
102108

103109
const stateSequence = 'i-v--w--x--y--z';
104110
const counter1Values = { i: 0, v: 1, w: 2, x: 1, y: 0, z: 1 };
@@ -109,7 +115,7 @@ describe('ngRx Store', () => {
109115
});
110116

111117
it('should correctly lift itself', () => {
112-
const result = store.select('counter1');
118+
const result = store.pipe(select('counter1'));
113119

114120
expect(result instanceof Store).toBe(true);
115121
});
@@ -119,7 +125,7 @@ describe('ngRx Store', () => {
119125

120126
counterSteps.subscribe(action => store.dispatch(action));
121127

122-
const counterState = store.select('counter1');
128+
const counterState = store.pipe(select('counter1'));
123129

124130
const stateSequence = 'i-v--w--x--y--z';
125131
const counter1Values = { i: 0, v: 1, w: 2, x: 1, y: 0, z: 1 };
@@ -132,7 +138,7 @@ describe('ngRx Store', () => {
132138

133139
counterSteps.subscribe(action => dispatcher.next(action));
134140

135-
const counterState = store.select('counter1');
141+
const counterState = store.pipe(select('counter1'));
136142

137143
const stateSequence = 'i-v--w--x--y--z';
138144
const counter1Values = { i: 0, v: 1, w: 2, x: 1, y: 0, z: 1 };
@@ -145,8 +151,8 @@ describe('ngRx Store', () => {
145151

146152
counterSteps.subscribe(action => store.dispatch(action));
147153

148-
const counter1State = store.select('counter1');
149-
const counter2State = store.select('counter2');
154+
const counter1State = store.pipe(select('counter1'));
155+
const counter2State = store.pipe(select('counter2'));
150156

151157
const stateSequence = 'i-v--w--x--y--z';
152158
const counter2Values = { i: 1, v: 2, w: 3, x: 2, y: 0, z: 1 };

modules/store/src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ export {
77
Selector,
88
} from './models';
99
export { StoreModule } from './store_module';
10-
export { Store } from './store';
10+
export { Store, select } from './store';
1111
export { combineReducers, compose, createReducerFactory } from './utils';
1212
export { ActionsSubject, INIT } from './actions_subject';
1313
export {

modules/store/src/store.ts

Lines changed: 81 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -65,20 +65,7 @@ export class Store<T> extends Observable<T> implements Observer<Action> {
6565
pathOrMapFn: ((state: T) => any) | string,
6666
...paths: string[]
6767
): Store<any> {
68-
let mapped$: Store<any>;
69-
70-
if (typeof pathOrMapFn === 'string') {
71-
mapped$ = pluck.call(this, pathOrMapFn, ...paths);
72-
} else if (typeof pathOrMapFn === 'function') {
73-
mapped$ = map.call(this, pathOrMapFn);
74-
} else {
75-
throw new TypeError(
76-
`Unexpected type '${typeof pathOrMapFn}' in select operator,` +
77-
` expected 'string' or 'function'`
78-
);
79-
}
80-
81-
return distinctUntilChanged.call(mapped$);
68+
return select(pathOrMapFn, ...paths)(this);
8269
}
8370

8471
lift<R>(operator: Operator<T, R>): Store<R> {
@@ -117,3 +104,83 @@ export class Store<T> extends Observable<T> implements Observer<Action> {
117104
}
118105

119106
export const STORE_PROVIDERS: Provider[] = [Store];
107+
108+
export function select<T, K>(
109+
mapFn: ((state: T) => K) | string
110+
): (source$: Observable<T>) => Store<K>;
111+
export function select<T, a extends keyof T>(
112+
key: a
113+
): (source$: Store<a>) => Store<T[a]>;
114+
export function select<T, a extends keyof T, b extends keyof T[a]>(
115+
key1: a,
116+
key2: b
117+
): (source$: Store<T>) => Store<T[a][b]>;
118+
export function select<
119+
T,
120+
a extends keyof T,
121+
b extends keyof T[a],
122+
c extends keyof T[a][b]
123+
>(key1: a, key2: b, key3: c): (source$: Store<a>) => Store<T[a][b][c]>;
124+
export function select<
125+
T,
126+
a extends keyof T,
127+
b extends keyof T[a],
128+
c extends keyof T[a][b],
129+
d extends keyof T[a][b][c]
130+
>(
131+
key1: a,
132+
key2: b,
133+
key3: c,
134+
key4: d
135+
): (source$: Store<a>) => Store<T[a][b][c][d]>;
136+
export function select<
137+
T,
138+
a extends keyof T,
139+
b extends keyof T[a],
140+
c extends keyof T[a][b],
141+
d extends keyof T[a][b][c],
142+
e extends keyof T[a][b][c][d]
143+
>(
144+
key1: a,
145+
key2: b,
146+
key3: c,
147+
key4: d,
148+
key5: e
149+
): (source$: Store<a>) => Store<T[a][b][c][d][e]>;
150+
export function select<
151+
T,
152+
a extends keyof T,
153+
b extends keyof T[a],
154+
c extends keyof T[a][b],
155+
d extends keyof T[a][b][c],
156+
e extends keyof T[a][b][c][d],
157+
f extends keyof T[a][b][c][d][e]
158+
>(
159+
key1: a,
160+
key2: b,
161+
key3: c,
162+
key4: d,
163+
key5: e,
164+
key6: f
165+
): (source$: Store<a>) => Store<T[a][b][c][d][e][f]>;
166+
export function select<T, K>(
167+
pathOrMapFn: ((state: T) => any) | string,
168+
...paths: string[]
169+
) {
170+
return function selectOperator(source$: Store<T>): Store<K> {
171+
let mapped$: Store<any>;
172+
173+
if (typeof pathOrMapFn === 'string') {
174+
mapped$ = pluck.call(source$, pathOrMapFn, ...paths);
175+
} else if (typeof pathOrMapFn === 'function') {
176+
mapped$ = map.call(source$, pathOrMapFn);
177+
} else {
178+
throw new TypeError(
179+
`Unexpected type '${typeof pathOrMapFn}' in select operator,` +
180+
` expected 'string' or 'function'`
181+
);
182+
}
183+
184+
return distinctUntilChanged.call(mapped$);
185+
};
186+
}

0 commit comments

Comments
 (0)