Skip to content

Commit 03db76f

Browse files
timdeschryverbrandonroberts
authored andcommitted
feat(store): add an overload to createFeatureSelector to provide better type checking (#1171)
* Also refactors the example app to use typed version of createFeatureSelector Closes #1136
1 parent e7ae8a2 commit 03db76f

File tree

5 files changed

+34
-22
lines changed

5 files changed

+34
-22
lines changed

docs/store/selectors.md

Lines changed: 22 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -97,13 +97,23 @@ export interface AppState {
9797
feature: FeatureState;
9898
}
9999

100-
export const selectFeature = createFeatureSelector<FeatureState>('feature');
100+
export const selectFeature = createFeatureSelector<AppState, FeatureState>(
101+
'feature'
102+
);
101103
export const selectFeatureCount = createSelector(
102104
selectFeature,
103105
(state: FeatureState) => state.counter
104106
);
105107
```
106108

109+
The following selector below would not compile because `foo` is not a feature slice of `AppState`.
110+
111+
```ts
112+
export const selectFeature = createFeatureSelector<AppState, FeatureState>(
113+
'foo'
114+
);
115+
```
116+
107117
## Reset Memoized Selector
108118

109119
The selector function returned by calling `createSelector` or `createFeatureSelector` initially has a memoized value of `null`. After a selector is invoked the first time its memoized value is stored in memory. If the selector is subsequently invoked with the same arguments it will return the memoized value. If the selector is then invoked with different arguments it will recompute and update its memoized value. Consider the following:
@@ -234,9 +244,7 @@ import { filter } from 'rxjs/operators';
234244

235245
store
236246
.select(selectValues)
237-
.pipe(
238-
filter(val => val !== undefined)
239-
)
247+
.pipe(filter(val => val !== undefined))
240248
.subscribe(/* .. */);
241249
```
242250

@@ -247,10 +255,9 @@ The same behaviour can be achieved by re-writing the above piece of code to use
247255
```ts
248256
import { map, filter } from 'rxjs/operators';
249257

250-
store.pipe(
251-
map(state => selectValues(state)),
252-
filter(val => val !== undefined)
253-
).subscribe(/* .. */);
258+
store
259+
.pipe(map(state => selectValues(state)), filter(val => val !== undefined))
260+
.subscribe(/* .. */);
254261
```
255262

256263
The above can be further re-written to use the `select()` utility function from NgRx:
@@ -259,10 +266,9 @@ The above can be further re-written to use the `select()` utility function from
259266
import { select } from '@ngrx/store';
260267
import { map, filter } from 'rxjs/operators';
261268

262-
store.pipe(
263-
select(selectValues(state)),
264-
filter(val => val !== undefined)
265-
).subscribe(/* .. */);
269+
store
270+
.pipe(select(selectValues(state)), filter(val => val !== undefined))
271+
.subscribe(/* .. */);
266272
```
267273

268274
#### Solution: Extracting a pipeable operator
@@ -279,15 +285,14 @@ export const selectFilteredValues = pipe(
279285
filter(val => val !== undefined)
280286
);
281287

282-
store.pipe(selectFilteredValues)
283-
.subscribe(/* .. */);
288+
store.pipe(selectFilteredValues).subscribe(/* .. */);
284289
```
285290

286291
### Advanced Example: Select the last {n} state transitions
287292

288293
Let's examine the technique of combining NgRx selectors and RxJS operators in an advanced example.
289294

290-
In this example, we will write a selector function that projects values from two different slices of the application state.
295+
In this example, we will write a selector function that projects values from two different slices of the application state.
291296
The projected state will emit a value when both slices of state have a value.
292297
Otherwise, the selector will emit an `undefined` value.
293298

@@ -307,7 +312,7 @@ export const selectProjectedValues = createSelector(
307312

308313
Then, the component should visualize the history of state transitions.
309314
We are not only interested in the current state but rather like to display the last `n` pieces of state.
310-
Meaning that we will map a stream of state values (`1`, `2`, `3`) to an array of state values (`[1, 2, 3]`).
315+
Meaning that we will map a stream of state values (`1`, `2`, `3`) to an array of state values (`[1, 2, 3]`).
311316

312317
```ts
313318
// The number of state transitions is given by the user (subscriber)
@@ -329,8 +334,7 @@ Finally, the component will subscribe to the store, telling the number of state
329334

330335
```ts
331336
// Subscribe to the store using the custom pipeable operator
332-
store.pipe(selectLastStateTransitions(3))
333-
.subscribe(/* .. */);
337+
store.pipe(selectLastStateTransitions(3)).subscribe(/* .. */);
334338
```
335339

336340
See the [advanced example live in action in a Stackblitz](https://stackblitz.com/edit/angular-ngrx-effects-1rj88y?file=app%2Fstore%2Ffoo.ts)

example-app/app/auth/reducers/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ export const reducers: ActionReducerMap<AuthState> = {
2121
loginPage: fromLoginPage.reducer,
2222
};
2323

24-
export const selectAuthState = createFeatureSelector<AuthState>('auth');
24+
export const selectAuthState = createFeatureSelector<State, AuthState>('auth');
2525

2626
export const selectAuthStatusState = createSelector(
2727
selectAuthState,

example-app/app/books/reducers/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ export const reducers: ActionReducerMap<BooksState> = {
4444
* The createFeatureSelector function selects a piece of state from the root of the state object.
4545
* This is used for selecting feature states that are loaded eagerly or lazily.
4646
*/
47-
export const getBooksState = createFeatureSelector<BooksState>('books');
47+
export const getBooksState = createFeatureSelector<State, BooksState>('books');
4848

4949
/**
5050
* Every reducer module exports selector functions, however child reducers

example-app/app/reducers/index.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,9 @@ export const metaReducers: MetaReducer<State>[] = !environment.production
6565
/**
6666
* Layout Reducers
6767
*/
68-
export const getLayoutState = createFeatureSelector<fromLayout.State>('layout');
68+
export const getLayoutState = createFeatureSelector<State, fromLayout.State>(
69+
'layout'
70+
);
6971

7072
export const getShowSidenav = createSelector(
7173
getLayoutState,

modules/store/src/selector.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -269,7 +269,13 @@ export function createSelectorFactory(
269269

270270
export function createFeatureSelector<T>(
271271
featureName: string
272-
): MemoizedSelector<object, T> {
272+
): MemoizedSelector<object, T>;
273+
export function createFeatureSelector<T, V>(
274+
featureName: keyof T
275+
): MemoizedSelector<T, V>;
276+
export function createFeatureSelector(
277+
featureName: any
278+
): MemoizedSelector<any, any> {
273279
return createSelector(
274280
(state: any) => state[featureName],
275281
(featureState: any) => featureState

0 commit comments

Comments
 (0)