diff --git a/modules/router-store/spec/integration.spec.ts b/modules/router-store/spec/integration.spec.ts index 0aa23ebc9c..9c753ad729 100644 --- a/modules/router-store/spec/integration.spec.ts +++ b/modules/router-store/spec/integration.spec.ts @@ -1,3 +1,4 @@ +import { StoreRouterConfig } from '../src/router_store_module'; import { Component, Provider } from '@angular/core'; import { TestBed } from '@angular/core/testing'; import { NavigationEnd, Router, RouterStateSnapshot } from '@angular/router'; @@ -457,7 +458,59 @@ describe('integration spec', () => { { type: 'router', event: 'GuardsCheckEnd', url: '/next' }, { type: 'router', event: 'ResolveStart', url: '/next' }, { type: 'router', event: 'ResolveEnd', url: '/next' }, + { type: 'router', event: 'NavigationEnd', url: '/next' }, + ]); + done(); + }); + }); + + it('should work when defining state key', (done: any) => { + const reducer = (state: string = '', action: RouterAction) => { + if (action.type === ROUTER_NAVIGATION) { + return action.payload.routerState.url.toString(); + } else { + return state; + } + }; + + createTestModule({ + reducers: { reducer }, + config: { stateKey: 'router-reducer' }, + }); + + const router: Router = TestBed.get(Router); + const store = TestBed.get(Store); + const log = logOfRouterAndStore(router, store); + + router + .navigateByUrl('/') + .then(() => { + expect(log).toEqual([ + { type: 'store', state: '' }, // init event. has nothing to do with the router + { type: 'router', event: 'NavigationStart', url: '/' }, + { type: 'router', event: 'RoutesRecognized', url: '/' }, + { type: 'store', state: '/' }, // ROUTER_NAVIGATION event in the store + { type: 'router', event: 'GuardsCheckStart', url: '/' }, + { type: 'router', event: 'GuardsCheckEnd', url: '/' }, + { type: 'router', event: 'ResolveStart', url: '/' }, + { type: 'router', event: 'ResolveEnd', url: '/' }, + { type: 'router', event: 'NavigationEnd', url: '/' }, + ]); + }) + .then(() => { + log.splice(0); + return router.navigateByUrl('next'); + }) + .then(() => { + expect(log).toEqual([ + { type: 'router', event: 'NavigationStart', url: '/next' }, + { type: 'router', event: 'RoutesRecognized', url: '/next' }, + { type: 'store', state: '/next' }, + { type: 'router', event: 'GuardsCheckStart', url: '/next' }, + { type: 'router', event: 'GuardsCheckEnd', url: '/next' }, + { type: 'router', event: 'ResolveStart', url: '/next' }, + { type: 'router', event: 'ResolveEnd', url: '/next' }, { type: 'router', event: 'NavigationEnd', url: '/next' }, ]); @@ -472,6 +525,7 @@ function createTestModule( canActivate?: Function; canLoad?: Function; providers?: Provider[]; + config?: StoreRouterConfig; } = {} ) { @Component({ @@ -503,7 +557,7 @@ function createTestModule( canLoad: ['CanLoadNext'], }, ]), - StoreRouterConnectingModule, + StoreRouterConnectingModule.forRoot(opts.config), ], providers: [ { diff --git a/modules/router-store/src/index.ts b/modules/router-store/src/index.ts index ef9b177f2e..550f4d39b5 100644 --- a/modules/router-store/src/index.ts +++ b/modules/router-store/src/index.ts @@ -12,6 +12,10 @@ export { RouterCancelPayload, RouterNavigationPayload, StoreRouterConnectingModule, + StoreRouterConfig, + StoreRouterConfigFunction, + ROUTER_CONFIG, + DEFAULT_ROUTER_FEATURENAME, } from './router_store_module'; export { diff --git a/modules/router-store/src/router_store_module.ts b/modules/router-store/src/router_store_module.ts index b65a5990b3..1b805f88b6 100644 --- a/modules/router-store/src/router_store_module.ts +++ b/modules/router-store/src/router_store_module.ts @@ -1,4 +1,9 @@ -import { NgModule } from '@angular/core'; +import { + NgModule, + ModuleWithProviders, + InjectionToken, + Inject, +} from '@angular/core'; import { NavigationCancel, NavigationError, @@ -107,6 +112,33 @@ export function routerReducer( } } +export type StoreRouterConfig = { + stateKey?: string; +}; + +export const _ROUTER_CONFIG = new InjectionToken( + '@ngrx/router-store Internal Configuration' +); +export const ROUTER_CONFIG = new InjectionToken( + '@ngrx/router-store Configuration' +); +export const DEFAULT_ROUTER_FEATURENAME = 'routerReducer'; + +export function _createDefaultRouterConfig(config: any): StoreRouterConfig { + let _config = {}; + + if (typeof config === 'function') { + _config = config(); + } + + return { + stateKey: DEFAULT_ROUTER_FEATURENAME, + ..._config, + }; +} + +export type StoreRouterConfigFunction = () => StoreRouterConfig; + /** * Connects RouterModule with StoreModule. * @@ -152,9 +184,37 @@ export function routerReducer( @NgModule({ providers: [ { provide: RouterStateSerializer, useClass: DefaultRouterStateSerializer }, + { + provide: _ROUTER_CONFIG, + useValue: { stateKey: DEFAULT_ROUTER_FEATURENAME }, + }, + { + provide: ROUTER_CONFIG, + useFactory: _createDefaultRouterConfig, + deps: [_ROUTER_CONFIG], + }, ], }) export class StoreRouterConnectingModule { + static forRoot( + config?: StoreRouterConfig | StoreRouterConfigFunction + ): ModuleWithProviders; + static forRoot( + config: StoreRouterConfig | StoreRouterConfigFunction = {} + ): ModuleWithProviders { + return { + ngModule: StoreRouterConnectingModule, + providers: [ + { provide: _ROUTER_CONFIG, useValue: config }, + { + provide: ROUTER_CONFIG, + useFactory: _createDefaultRouterConfig, + deps: [_ROUTER_CONFIG], + }, + ], + }; + } + private routerState: RouterStateSnapshot; private storeState: any; private lastRoutesRecognized: RoutesRecognized; @@ -162,11 +222,16 @@ export class StoreRouterConnectingModule { private dispatchTriggeredByRouter: boolean = false; // used only in dev mode in combination with routerReducer private navigationTriggeredByDispatch: boolean = false; // used only in dev mode in combination with routerReducer + private stateKey: string; + constructor( private store: Store, private router: Router, - private serializer: RouterStateSerializer + private serializer: RouterStateSerializer, + @Inject(ROUTER_CONFIG) private config: StoreRouterConfig ) { + this.stateKey = this.config.stateKey as string; + this.setUpBeforePreactivationHook(); this.setUpStoreStateListener(); this.setUpStateRollbackEvents(); @@ -187,28 +252,28 @@ export class StoreRouterConnectingModule { this.store.subscribe(s => { this.storeState = s; }); - this.store.select('routerReducer').subscribe(() => { + this.store.select(this.stateKey).subscribe(() => { this.navigateIfNeeded(); }); } private shouldDispatchRouterNavigation(): boolean { - if (!this.storeState['routerReducer']) return true; + if (!this.storeState[this.stateKey]) return true; return !this.navigationTriggeredByDispatch; } private navigateIfNeeded(): void { if ( - !this.storeState['routerReducer'] || - !this.storeState['routerReducer'].state + !this.storeState[this.stateKey] || + !this.storeState[this.stateKey].state ) { return; } if (this.dispatchTriggeredByRouter) return; - if (this.router.url !== this.storeState['routerReducer'].state.url) { + if (this.router.url !== this.storeState[this.stateKey].state.url) { this.navigationTriggeredByDispatch = true; - this.router.navigateByUrl(this.storeState['routerReducer'].state.url); + this.router.navigateByUrl(this.storeState[this.stateKey].state.url); } } diff --git a/yarn.lock b/yarn.lock index 45aefddacf..a28e354c7d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7873,4 +7873,4 @@ zone.js@^0.8.12: zone.js@^0.8.14: version "0.8.18" - resolved "https://registry.yarnpkg.com/zone.js/-/zone.js-0.8.18.tgz#8cecb3977fcd1b3090562ff4570e2847e752b48d" + resolved "https://registry.yarnpkg.com/zone.js/-/zone.js-0.8.18.tgz#8cecb3977fcd1b3090562ff4570e2847e752b48d" \ No newline at end of file