Skip to content

Commit

Permalink
feat(store): add an overload to createFeatureSelector to provide bett…
Browse files Browse the repository at this point in the history
…er type checking (#1171)

* Also refactors the example app to use typed version of createFeatureSelector

Closes #1136
  • Loading branch information
timdeschryver authored and brandonroberts committed Jul 5, 2018
1 parent e7ae8a2 commit 03db76f
Show file tree
Hide file tree
Showing 5 changed files with 34 additions and 22 deletions.
40 changes: 22 additions & 18 deletions docs/store/selectors.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,13 +97,23 @@ export interface AppState {
feature: FeatureState;
}

export const selectFeature = createFeatureSelector<FeatureState>('feature');
export const selectFeature = createFeatureSelector<AppState, FeatureState>(
'feature'
);
export const selectFeatureCount = createSelector(
selectFeature,
(state: FeatureState) => state.counter
);
```

The following selector below would not compile because `foo` is not a feature slice of `AppState`.

```ts
export const selectFeature = createFeatureSelector<AppState, FeatureState>(
'foo'
);
```

## Reset Memoized Selector

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:
Expand Down Expand Up @@ -234,9 +244,7 @@ import { filter } from 'rxjs/operators';

store
.select(selectValues)
.pipe(
filter(val => val !== undefined)
)
.pipe(filter(val => val !== undefined))
.subscribe(/* .. */);
```

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

store.pipe(
map(state => selectValues(state)),
filter(val => val !== undefined)
).subscribe(/* .. */);
store
.pipe(map(state => selectValues(state)), filter(val => val !== undefined))
.subscribe(/* .. */);
```

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

store.pipe(
select(selectValues(state)),
filter(val => val !== undefined)
).subscribe(/* .. */);
store
.pipe(select(selectValues(state)), filter(val => val !== undefined))
.subscribe(/* .. */);
```

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

store.pipe(selectFilteredValues)
.subscribe(/* .. */);
store.pipe(selectFilteredValues).subscribe(/* .. */);
```

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

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

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

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

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

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

```ts
// Subscribe to the store using the custom pipeable operator
store.pipe(selectLastStateTransitions(3))
.subscribe(/* .. */);
store.pipe(selectLastStateTransitions(3)).subscribe(/* .. */);
```

See the [advanced example live in action in a Stackblitz](https://stackblitz.com/edit/angular-ngrx-effects-1rj88y?file=app%2Fstore%2Ffoo.ts)
2 changes: 1 addition & 1 deletion example-app/app/auth/reducers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export const reducers: ActionReducerMap<AuthState> = {
loginPage: fromLoginPage.reducer,
};

export const selectAuthState = createFeatureSelector<AuthState>('auth');
export const selectAuthState = createFeatureSelector<State, AuthState>('auth');

export const selectAuthStatusState = createSelector(
selectAuthState,
Expand Down
2 changes: 1 addition & 1 deletion example-app/app/books/reducers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ export const reducers: ActionReducerMap<BooksState> = {
* The createFeatureSelector function selects a piece of state from the root of the state object.
* This is used for selecting feature states that are loaded eagerly or lazily.
*/
export const getBooksState = createFeatureSelector<BooksState>('books');
export const getBooksState = createFeatureSelector<State, BooksState>('books');

/**
* Every reducer module exports selector functions, however child reducers
Expand Down
4 changes: 3 additions & 1 deletion example-app/app/reducers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,9 @@ export const metaReducers: MetaReducer<State>[] = !environment.production
/**
* Layout Reducers
*/
export const getLayoutState = createFeatureSelector<fromLayout.State>('layout');
export const getLayoutState = createFeatureSelector<State, fromLayout.State>(
'layout'
);

export const getShowSidenav = createSelector(
getLayoutState,
Expand Down
8 changes: 7 additions & 1 deletion modules/store/src/selector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -269,7 +269,13 @@ export function createSelectorFactory(

export function createFeatureSelector<T>(
featureName: string
): MemoizedSelector<object, T> {
): MemoizedSelector<object, T>;
export function createFeatureSelector<T, V>(
featureName: keyof T
): MemoizedSelector<T, V>;
export function createFeatureSelector(
featureName: any
): MemoizedSelector<any, any> {
return createSelector(
(state: any) => state[featureName],
(featureState: any) => featureState
Expand Down

0 comments on commit 03db76f

Please sign in to comment.