Skip to content

Commit

Permalink
feat(router-store): add createRouterSelector to select router data fo…
Browse files Browse the repository at this point in the history
…r default config (#3103)
  • Loading branch information
timdeschryver authored Aug 4, 2021
1 parent 23c846b commit 507f58e
Show file tree
Hide file tree
Showing 5 changed files with 74 additions and 44 deletions.
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 Record<string, any>>(): 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

0 comments on commit 507f58e

Please sign in to comment.