Skip to content

Commit

Permalink
feat(router-store): add provideRouterStore function (#3532)
Browse files Browse the repository at this point in the history
Closes #3528
  • Loading branch information
yharaskrik committed Aug 24, 2022
1 parent 5639c1e commit 511b7cf
Show file tree
Hide file tree
Showing 9 changed files with 423 additions and 355 deletions.
38 changes: 21 additions & 17 deletions modules/router-store/spec/router_store_module.spec.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,24 @@
import { TestBed } from '@angular/core/testing';
import { Router, RouterEvent, NavigationEnd } from '@angular/router';
import { NavigationEnd, Router, RouterEvent } from '@angular/router';
import {
FullRouterStateSerializer,
MinimalRouterStateSerializer,
RouterAction,
routerReducer,
RouterReducerState,
StoreRouterConnectingModule,
RouterAction,
RouterState,
RouterStateSerializer,
MinimalRouterStateSerializer,
FullRouterStateSerializer,
} from '@ngrx/router-store';
import { select, Store, ActionsSubject } from '@ngrx/store';
import { withLatestFrom, filter, skip } from 'rxjs/operators';
import { ActionsSubject, select, Store } from '@ngrx/store';
import { filter, withLatestFrom } from 'rxjs/operators';

import { createTestModule } from './utils';
import { StoreRouterConnectingService } from '../src/store_router_connecting.service';

describe('Router Store Module', () => {
describe('with defining state key', () => {
const customStateKey = 'router-reducer';
let storeRouterConnectingModule: StoreRouterConnectingModule;
let storeRouterConnectingService: StoreRouterConnectingService;
let store: Store<State>;
let router: Router;

Expand All @@ -38,11 +38,13 @@ describe('Router Store Module', () => {

store = TestBed.inject(Store);
router = TestBed.inject(Router);
storeRouterConnectingModule = TestBed.inject(StoreRouterConnectingModule);
storeRouterConnectingService = TestBed.inject(
StoreRouterConnectingService
);
});

it('should have custom state key as own property', () => {
expect((<any>storeRouterConnectingModule).stateKey).toBe(customStateKey);
expect((<any>storeRouterConnectingService).stateKey).toBe(customStateKey);
});

it('should call navigateIfNeeded with args selected by custom state key', (done: any) => {
Expand All @@ -54,7 +56,7 @@ describe('Router Store Module', () => {
});

spyOn(
storeRouterConnectingModule,
storeRouterConnectingService,
'navigateIfNeeded' as never
).and.callThrough();
logs = [];
Expand All @@ -63,7 +65,7 @@ describe('Router Store Module', () => {
// and store emits its payload.
router.navigateByUrl('/').then(() => {
const actual = (<any>(
storeRouterConnectingModule
storeRouterConnectingService
)).navigateIfNeeded.calls.allArgs();

expect(actual.length).toBe(1);
Expand All @@ -77,7 +79,7 @@ describe('Router Store Module', () => {
const customStateKey = 'routerReducer';
const customStateSelector = (state: State) => state.routerReducer;

let storeRouterConnectingModule: StoreRouterConnectingModule;
let storeRouterConnectingService: StoreRouterConnectingService;
let store: Store<State>;
let router: Router;

Expand All @@ -97,11 +99,13 @@ describe('Router Store Module', () => {

store = TestBed.inject(Store);
router = TestBed.inject(Router);
storeRouterConnectingModule = TestBed.inject(StoreRouterConnectingModule);
storeRouterConnectingService = TestBed.inject(
StoreRouterConnectingService
);
});

it('should have same state selector as own property', () => {
expect((<any>storeRouterConnectingModule).stateKey).toBe(
expect((<any>storeRouterConnectingService).stateKey).toBe(
customStateSelector
);
});
Expand All @@ -115,7 +119,7 @@ describe('Router Store Module', () => {
});

spyOn(
storeRouterConnectingModule,
storeRouterConnectingService,
'navigateIfNeeded' as never
).and.callThrough();
logs = [];
Expand All @@ -124,7 +128,7 @@ describe('Router Store Module', () => {
// and store emits its payload.
router.navigateByUrl('/').then(() => {
const actual = (<any>(
storeRouterConnectingModule
storeRouterConnectingService
)).navigateIfNeeded.calls.allArgs();

expect(actual.length).toBe(1);
Expand Down
5 changes: 3 additions & 2 deletions modules/router-store/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,15 @@ export {
routerRequestAction,
} from './actions';
export { routerReducer, RouterReducerState } from './reducer';
export { StoreRouterConnectingModule } from './router_store_module';
export {
StateKeyOrSelector,
StoreRouterConnectingModule,
StoreRouterConfig,
NavigationActionTiming,
ROUTER_CONFIG,
DEFAULT_ROUTER_FEATURENAME,
RouterState,
} from './router_store_module';
} from './router_store_config';
export {
RouterStateSerializer,
BaseRouterStoreState,
Expand All @@ -45,3 +45,4 @@ export {
MinimalRouterStateSerializer,
} from './serializers/minimal_serializer';
export { getSelectors, createRouterSelector } from './router_selectors';
export { provideRouterStore } from './provide_router_store';
2 changes: 1 addition & 1 deletion modules/router-store/src/models.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Params, Data } from '@angular/router';
import { Data, Params } from '@angular/router';
import { MemoizedSelector } from '@ngrx/store';

export interface RouterStateSelectors<V> {
Expand Down
63 changes: 63 additions & 0 deletions modules/router-store/src/provide_router_store.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { ENVIRONMENT_INITIALIZER, inject, Provider } from '@angular/core';
import {
_createRouterConfig,
_ROUTER_CONFIG,
ROUTER_CONFIG,
RouterState,
StoreRouterConfig,
} from './router_store_config';
import {
FullRouterStateSerializer,
SerializedRouterStateSnapshot,
} from './serializers/full_serializer';
import { MinimalRouterStateSerializer } from './serializers/minimal_serializer';
import {
BaseRouterStoreState,
RouterStateSerializer,
} from './serializers/base';
import { StoreRouterConnectingService } from './store_router_connecting.service';
import { EnvironmentProviders } from '@ngrx/store';

/**
* Connects the Angular Router to the Store.
*
* @usageNotes
*
* ```typescript
* bootstrapApplication(AppComponent, {
* providers: [
* provideRouterStore()
* ]
* })
* ```
*/
export function provideRouterStore<
T extends BaseRouterStoreState = SerializedRouterStateSnapshot
>(config: StoreRouterConfig<T> = {}): EnvironmentProviders {
return {
ɵproviders: [
{ provide: _ROUTER_CONFIG, useValue: config },
{
provide: ROUTER_CONFIG,
useFactory: _createRouterConfig,
deps: [_ROUTER_CONFIG],
},
{
provide: RouterStateSerializer,
useClass: config.serializer
? config.serializer
: config.routerState === RouterState.Full
? FullRouterStateSerializer
: MinimalRouterStateSerializer,
},
{
provide: ENVIRONMENT_INITIALIZER,
multi: true,
useFactory() {
return () => inject(StoreRouterConnectingService);
},
},
StoreRouterConnectingService,
],
};
}
2 changes: 1 addition & 1 deletion modules/router-store/src/router_selectors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {
} from '@ngrx/store';
import { RouterStateSelectors } from './models';
import { RouterReducerState } from './reducer';
import { DEFAULT_ROUTER_FEATURENAME } from './router_store_module';
import { DEFAULT_ROUTER_FEATURENAME } from './router_store_config';

export function createRouterSelector<
State extends Record<string, any>
Expand Down
67 changes: 67 additions & 0 deletions modules/router-store/src/router_store_config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { InjectionToken } from '@angular/core';
import { Selector } from '@ngrx/store';
import { RouterReducerState } from './reducer';
import {
BaseRouterStoreState,
RouterStateSerializer,
} from './serializers/base';
import { SerializedRouterStateSnapshot } from './serializers/full_serializer';
import { MinimalRouterStateSerializer } from './serializers/minimal_serializer';

export type StateKeyOrSelector<
T extends BaseRouterStoreState = SerializedRouterStateSnapshot
> = string | Selector<any, RouterReducerState<T>>;

export enum NavigationActionTiming {
PreActivation = 1,
PostActivation = 2,
}
export const DEFAULT_ROUTER_FEATURENAME = 'router';

export const _ROUTER_CONFIG = new InjectionToken(
'@ngrx/router-store Internal Configuration'
);
export const ROUTER_CONFIG = new InjectionToken(
'@ngrx/router-store Configuration'
);

/**
* Minimal = Serializes the router event with MinimalRouterStateSerializer
* Full = Serializes the router event with FullRouterStateSerializer
*/
export const enum RouterState {
Full,
Minimal,
}

export function _createRouterConfig(
config: StoreRouterConfig
): StoreRouterConfig {
return {
stateKey: DEFAULT_ROUTER_FEATURENAME,
serializer: MinimalRouterStateSerializer,
navigationActionTiming: NavigationActionTiming.PreActivation,
...config,
};
}

export interface StoreRouterConfig<
T extends BaseRouterStoreState = SerializedRouterStateSnapshot
> {
stateKey?: StateKeyOrSelector<T>;
serializer?: new (...args: any[]) => RouterStateSerializer;
/**
* By default, ROUTER_NAVIGATION is dispatched before guards and resolvers run.
* Therefore, the action could run too soon, for example
* there may be a navigation cancel due to a guard saying the navigation is not allowed.
* To run ROUTER_NAVIGATION after guards and resolvers,
* set this property to NavigationActionTiming.PostActivation.
*/
navigationActionTiming?: NavigationActionTiming;
/**
* Decides which router serializer should be used, if there is none provided, and the metadata on the dispatched @ngrx/router-store action payload.
* Set to `Minimal` to use the `MinimalRouterStateSerializer` and to set a minimal router event with the navigation id and url as payload.
* Set to `Full` to use the `FullRouterStateSerializer` and to set the angular router events as payload.
*/
routerState?: RouterState;
}

0 comments on commit 511b7cf

Please sign in to comment.