Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(Store): add an overload to createFeatureSelector to provide better type checking #1171

Merged
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 29 additions & 17 deletions docs/store/selectors.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,24 @@ export const selectFeatureCount = createSelector(
);
```

There is also an overload on the `createFeatureSelector` method, which allows a better typechecking.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of adding this as an additional section, let's just make this the default in the example above.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yea I wasn't sure about this, but you just confirmed my thinking.
Should I also update the example app?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes

The example from above would become:

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

Where `AppState` is the type from the top level feature state and `FeatureState` is the type from the feature slice to select.
Meaning that the following would not compile because `foo` is not a feature slice of `AppState`.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Keep the section starting with Meaning as a note.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Change to The following selector below would not ...

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No problem! Thanks for the reviews!

```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 +252,7 @@ import { filter } from 'rxjs/operators';

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

Expand All @@ -247,10 +263,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 +274,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 +293,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 +320,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 +342,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)
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