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 provideRouterStore function #3532

Merged
Show file tree
Hide file tree
Changes from all 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
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;
}
Loading