From 21c67ccce5ce2d87885c3691286f9e0ef621d098 Mon Sep 17 00:00:00 2001 From: Jason Hodges Date: Wed, 29 May 2019 16:56:02 -0600 Subject: [PATCH] feat(router-store): add selectors for router state (#1874) Closes #1854 --- .../spec/router_selectors.spec.ts | 164 ++++++++++++++++++ modules/router-store/src/index.ts | 1 + modules/router-store/src/models.ts | 9 + modules/router-store/src/router_selectors.ts | 49 ++++++ .../content/guide/router-store/selectors.md | 33 ++++ projects/ngrx.io/content/navigation.json | 4 + 6 files changed, 260 insertions(+) create mode 100644 modules/router-store/spec/router_selectors.spec.ts create mode 100644 modules/router-store/src/models.ts create mode 100644 modules/router-store/src/router_selectors.ts create mode 100644 projects/ngrx.io/content/guide/router-store/selectors.md diff --git a/modules/router-store/spec/router_selectors.spec.ts b/modules/router-store/spec/router_selectors.spec.ts new file mode 100644 index 0000000000..e170f467d1 --- /dev/null +++ b/modules/router-store/spec/router_selectors.spec.ts @@ -0,0 +1,164 @@ +import { RouterReducerState, getSelectors } from '@ngrx/router-store'; +import { RouterStateSelectors } from '../src/models'; + +const mockData = { + state: { + root: { + params: {}, + paramMap: { + params: {}, + }, + data: {}, + url: [], + outlet: 'primary', + routeConfig: null, + queryParams: {}, + queryParamMap: { + params: {}, + }, + fragment: null, + firstChild: { + params: {}, + paramMap: { + params: {}, + }, + data: {}, + url: [ + { + path: 'login', + parameters: {}, + }, + ], + outlet: 'primary', + routeConfig: { + path: 'login', + }, + queryParams: { + id: 3, + }, + queryParamMap: { + params: {}, + }, + firstChild: { + params: { + id: 'etyDDwAAQBAJ', + }, + paramMap: { + params: { + id: 'etyDDwAAQBAJ', + }, + }, + data: { + testData: 'test-data', + }, + url: [ + { + path: 'etyDDwAAQBAJ', + parameters: {}, + }, + ], + outlet: 'primary', + routeConfig: { + path: ':id', + }, + queryParams: {}, + queryParamMap: { + params: {}, + }, + children: [], + }, + fragment: null, + children: [], + }, + children: [ + { + params: {}, + paramMap: { + params: {}, + }, + data: {}, + url: [ + { + path: 'login', + parameters: {}, + }, + ], + outlet: 'primary', + routeConfig: { + path: 'login', + }, + queryParams: {}, + queryParamMap: { + params: {}, + }, + children: [], + }, + ], + }, + url: '/login', + }, + navigationId: 1, +}; +describe('Router State Selectors', () => { + describe('Composed Selectors', () => { + interface State { + router: RouterReducerState; + } + + let selectors: RouterStateSelectors; + let state: State; + + beforeEach(() => { + state = { + router: mockData, + }; + + selectors = getSelectors((state: State) => state.router); + }); + it('should create selectCurrentRoute selector for selecting the current route', () => { + const result = selectors.selectCurrentRoute(state); + + expect(result).toEqual(state.router.state.root.firstChild.firstChild); + }); + it('should return undefined from selectCurrentRoute if routerState does not exist', () => { + interface State { + router: any; + } + let state: State; + state = { + router: undefined, + }; + selectors = getSelectors((state: State) => state.router); + + const result = selectors.selectCurrentRoute(state); + + expect(result).toEqual(undefined); + }); + it('should create a selector for selecting the query params', () => { + const result = selectors.selectQueryParams(state); + + expect(result).toEqual( + state.router.state.root.firstChild.firstChild.queryParams + ); + }); + it('should create a selector for selecting the route params', () => { + const result = selectors.selectRouteParams(state); + + expect(result).toEqual( + state.router.state.root.firstChild.firstChild.params + ); + }); + it('should create a selector for selecting the route data', () => { + const result = selectors.selectRouteData(state); + + expect(result).toEqual( + state.router.state.root.firstChild.firstChild.data + ); + }); + it('should create a selector for selecting the url', () => { + const result = selectors.selectUrl(state); + + expect(result).toEqual(state.router.state.url); + }); + }); +}); diff --git a/modules/router-store/src/index.ts b/modules/router-store/src/index.ts index 5c9915281a..c97b2a4e77 100644 --- a/modules/router-store/src/index.ts +++ b/modules/router-store/src/index.ts @@ -35,3 +35,4 @@ export { MinimalRouterStateSnapshot, MinimalRouterStateSerializer, } from './serializers'; +export { getSelectors } from './router_selectors'; diff --git a/modules/router-store/src/models.ts b/modules/router-store/src/models.ts new file mode 100644 index 0000000000..4ccd1d7e3b --- /dev/null +++ b/modules/router-store/src/models.ts @@ -0,0 +1,9 @@ +import { Data, Params } from '@angular/router'; + +export interface RouterStateSelectors { + selectCurrentRoute: (state: V) => any; + selectQueryParams: (state: V) => Params; + selectRouteParams: (state: V) => Params; + selectRouteData: (state: V) => Data; + selectUrl: (state: V) => string; +} diff --git a/modules/router-store/src/router_selectors.ts b/modules/router-store/src/router_selectors.ts new file mode 100644 index 0000000000..4008eca58a --- /dev/null +++ b/modules/router-store/src/router_selectors.ts @@ -0,0 +1,49 @@ +import { RouterReducerState } from '@ngrx/router-store'; +import { createSelector } from '@ngrx/store'; +import { RouterStateSelectors } from './models'; + +export function getSelectors( + selectState: (state: V) => RouterReducerState +): RouterStateSelectors; +export function getSelectors( + selectState: (state: V) => RouterReducerState +): RouterStateSelectors { + const selectRouterState = createSelector( + selectState, + router => router && router.state + ); + const selectCurrentRoute = createSelector(selectRouterState, routerState => { + if (!routerState) { + return undefined; + } + let route = routerState.root; + while (route.firstChild) { + route = route.firstChild; + } + return route; + }); + const selectQueryParams = createSelector( + selectCurrentRoute, + route => route && route.queryParams + ); + const selectRouteParams = createSelector( + selectCurrentRoute, + route => route && route.params + ); + const selectRouteData = createSelector( + selectCurrentRoute, + route => route && route.data + ); + const selectUrl = createSelector( + selectRouterState, + routerState => routerState && routerState.url + ); + + return { + selectCurrentRoute, + selectQueryParams, + selectRouteParams, + selectRouteData, + selectUrl, + }; +} diff --git a/projects/ngrx.io/content/guide/router-store/selectors.md b/projects/ngrx.io/content/guide/router-store/selectors.md new file mode 100644 index 0000000000..0117dc679f --- /dev/null +++ b/projects/ngrx.io/content/guide/router-store/selectors.md @@ -0,0 +1,33 @@ +# Router selectors + +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 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. + +Usage: + +`reducers/index.ts` + +```ts +import { getSelectors, RouterReducerState } from '@ngrx/router-store'; + +export interface State { + router: fromRouter.RouterReducerState; +} + +export const selectRouter = createFeatureSelector< + State, + fromRouter.RouterReducerState +>('router'); + +const { + selectQueryParams, // select the current route query params + selectRouteParams, // select the current route params + selectRouteData, // select the current route data + selectUrl, // select the current url +} = getSelectors(selectRouter); + +``` diff --git a/projects/ngrx.io/content/navigation.json b/projects/ngrx.io/content/navigation.json index 47b1c3a842..0a0bd1c22d 100644 --- a/projects/ngrx.io/content/navigation.json +++ b/projects/ngrx.io/content/navigation.json @@ -192,6 +192,10 @@ "title": "Actions", "url": "guide/router-store/actions" }, + { + "title": "Selectors", + "url": "guide/router-store/selectors" + }, { "title": "Configuration", "url": "guide/router-store/configuration"