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(router-store): add createRouterSelector to select router data for default config #3103

Merged
merged 3 commits into from
Aug 4, 2021
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
36 changes: 34 additions & 2 deletions modules/router-store/spec/router_selectors.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
import { RouterReducerState, getSelectors } from '@ngrx/router-store';
import {
getSelectors,
RouterReducerState,
DEFAULT_ROUTER_FEATURENAME,
createRouterSelector,
} from '@ngrx/router-store';
import { RouterStateSelectors } from '../src/models';

const mockData = {
Expand Down Expand Up @@ -130,7 +135,7 @@ describe('Router State Selectors', () => {
router: mockData,
};

selectors = getSelectors((state: State) => state.router);
selectors = getSelectors();
});

it('should create selectCurrentRoute selector for selecting the current route', () => {
Expand All @@ -139,6 +144,33 @@ describe('Router State Selectors', () => {
expect(result).toEqual(state.router.state.root.firstChild.firstChild);
});

it('should be able to overwrite default router feature state name', () => {
const stateOverwrite = {
anotherRouterKey: mockData,
};
const selectorOverwrite = getSelectors(
(state: typeof stateOverwrite) => state.anotherRouterKey
);

const result = selectorOverwrite.selectCurrentRoute(stateOverwrite);
expect(result).toEqual(
stateOverwrite.anotherRouterKey.state.root.firstChild.firstChild
);
});

it('should be able to use DEFAULT_ROUTER_FEATURENAME and createRouterSelector to select router feature state', () => {
const stateOverwrite = {
[DEFAULT_ROUTER_FEATURENAME]: mockData,
};
const selectorOverwrite = getSelectors(createRouterSelector());

const result = selectorOverwrite.selectCurrentRoute(stateOverwrite);
expect(result).toEqual(
stateOverwrite[DEFAULT_ROUTER_FEATURENAME].state.root.firstChild
.firstChild
);
});

it('should return undefined from selectCurrentRoute if routerState does not exist', () => {
interface State {
router: any;
Expand Down
2 changes: 1 addition & 1 deletion modules/router-store/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,4 +44,4 @@ export {
MinimalRouterStateSnapshot,
MinimalRouterStateSerializer,
} from './serializers/minimal_serializer';
export { getSelectors } from './router_selectors';
export { getSelectors, createRouterSelector } from './router_selectors';
16 changes: 14 additions & 2 deletions modules/router-store/src/router_selectors.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,21 @@
import { createSelector } from '@ngrx/store';
import {
createFeatureSelector,
createSelector,
MemoizedSelector,
} from '@ngrx/store';
import { RouterStateSelectors } from './models';
import { RouterReducerState } from './reducer';
import { DEFAULT_ROUTER_FEATURENAME } from './router_store_module';

export function createRouterSelector<State extends {} = {}>(): MemoizedSelector<
State,
RouterReducerState
> {
return createFeatureSelector(DEFAULT_ROUTER_FEATURENAME);
}

export function getSelectors<V>(
selectState: (state: V) => RouterReducerState<any>
selectState: (state: V) => RouterReducerState<any> = createRouterSelector<V>()
): RouterStateSelectors<V> {
const selectRouterState = createSelector(
selectState,
Expand Down
6 changes: 1 addition & 5 deletions projects/example-app/src/app/reducers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,4 @@ export const selectShowSidenav = createSelector(
/**
* Router Selectors
*/
export const selectRouter = createFeatureSelector<fromRouter.RouterReducerState>(
'router'
);

export const { selectRouteData } = fromRouter.getSelectors(selectRouter);
export const { selectRouteData } = fromRouter.getSelectors();
58 changes: 24 additions & 34 deletions projects/ngrx.io/content/guide/router-store/selectors.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@

The `getSelectors` method supplied within `@ngrx/router-store` provides functions for selecting common information from the router state.

The `getSelectors` method takes a selector function as its only argument to select the piece of state where the router state is being stored.
The default behavior of `getSelectors` selects the router state for the `router` state key.
If the default router state config is overwritten with a different router state key, the `getSelectors` method takes a selector function to select the piece of state where the router state is being stored.
The example below shows how to provide a selector for the top level `router` key in your state object.

**Note:** The `getSelectors` method works with the `routerReducer` provided by `@ngrx/router-store`. If you use a [custom serializer](guide/router-store/configuration#custom-router-state-serializer), you'll need to provide your own selectors.
Expand All @@ -13,13 +14,8 @@ Usage:

[Full App Used In This Example](https://stackblitz.com/edit/ngrx-router-store-selectors?file=src/app/car.state.ts)

`router.selectors.ts`

```ts
import { getSelectors, RouterReducerState } from '@ngrx/router-store';
import { createFeatureSelector } from '@ngrx/store';

export const selectRouter = createFeatureSelector<RouterReducerState>('router');
<code-example header="router.selectors.ts">
import { getSelectors } from '@ngrx/router-store';

export const {
selectCurrentRoute, // select the current route
Expand All @@ -30,12 +26,10 @@ export const {
selectRouteParam, // factory function to select a route param
selectRouteData, // select the current route data
selectUrl, // select the current url
} = getSelectors(selectRouter);
```
} = getSelectors();
</code-example>

`car.reducer.ts`

```ts
<code-example header="car.reducer.ts" >
import { createReducer, on } from '@ngrx/store';
import { EntityState, createEntityAdapter } from '@ngrx/entity';
import { appInit } from './car.actions';
Expand All @@ -47,28 +41,26 @@ export interface Car {
model: string;
}

export type CarState = EntityState<Car>;
export type CarState = EntityState&lt;Car&gt;;

export const carAdapter = createEntityAdapter<Car>({
selectId: car => car.id,
export const carAdapter = createEntityAdapter&lt;Car&gt;({
selectId: car =&gt; car.id,
});

const initialState = carAdapter.getInitialState();

export const reducer = createReducer<CarState>(
export const reducer = createReducer&lt;CarState&gt;(
initialState,
on(appInit, (state, { cars }) => carAdapter.addMany(cars, state))
on(appInit, (state, { cars }) =&gt; carAdapter.addMany(cars, state))
);
```

`car.selectors.ts`
</code-example>

```ts
<code-example header="car.selectors.ts" >
import { createFeatureSelector, createSelector } from '@ngrx/store';
import { selectRouteParams } from '../router.selectors';
import { carAdapter, CarState } from './car.reducer';

export const carsFeatureSelector = createFeatureSelector<CarState>('cars');
export const carsFeatureSelector = createFeatureSelector&lt;CarState&gt;('cars');

const { selectEntities, selectAll } = carAdapter.getSelectors();

Expand All @@ -88,13 +80,11 @@ export const selectCars = createSelector(
export const selectCar = createSelector(
selectCarEntities,
selectRouteParams,
(cars, { carId }) => cars[carId]
(cars, { carId }) =&gt; cars[carId]
);
```

`car.component.ts`
</code-example>

```ts
<code-example header="car.component.ts" >
import { Component } from '@angular/core';
import { select, Store } from '@ngrx/store';
import { selectCar } from './car.selectors';
Expand All @@ -109,7 +99,7 @@ export class CarComponent {

constructor(private store: Store) {}
}
```
</code-example>

## Extracting all params in the current route

Expand Down Expand Up @@ -138,11 +128,11 @@ Using `selectRouteParam{s}` will get the `matched` param but not the `urlPath` p

If all params in the URL Tree need to be extracted (both `urlPath` and `matched`), the following custom selector can be used. It accumulates params of all the segments in the matched route:

```typescript
<code-example>
import { Params } from '@angular/router';
import { createSelector } from '@ngrx/store';

export const selectRouteNestedParams = createSelector(selectRouter, (router) => {
export const selectRouteNestedParams = createSelector(selectRouter, (router) =&gt; {
let currentRoute = router?.state?.root;
let params: Params = {};
while (currentRoute?.firstChild) {
Expand All @@ -155,9 +145,9 @@ export const selectRouteNestedParams = createSelector(selectRouter, (router) =>
return params;
});

export const selectRouteNestedParam = (param: string) =>
createSelector(selectRouteNestedParams, (params) => params && params[param]);
```
export const selectRouteNestedParam = (param: string) =&gt;
createSelector(selectRouteNestedParams, (params) =&gt; params &amp;&amp; params[param]);
</code-example>

<div class="alert is-important">

Expand Down