From d9d5a8b3ce6a395f09851b368b96780bade909ff Mon Sep 17 00:00:00 2001 From: Brandon Roberts Date: Fri, 21 Jul 2017 06:50:31 -0500 Subject: [PATCH] feat(Store): Added injection token option for feature modules --- .angular-cli.json | 2 +- circle.yml | 1 + example-app/app/books/reducers/index.ts | 7 +- example-app/app/reducers/index.ts | 1 + example-app/tsconfig.app.json | 8 +-- modules/effects/spec/effects_metadata.spec.ts | 2 +- modules/store/spec/modules.spec.ts | 20 ++++++ modules/store/src/index.ts | 7 ++ modules/store/src/store_module.ts | 69 +++++++++++++++++-- modules/store/src/tokens.ts | 22 +++++- package.json | 6 +- 11 files changed, 126 insertions(+), 19 deletions(-) diff --git a/.angular-cli.json b/.angular-cli.json index 25fe6261f4..1783b8c28b 100644 --- a/.angular-cli.json +++ b/.angular-cli.json @@ -6,7 +6,7 @@ "apps": [ { "root": "example-app", - "outDir": "dist", + "outDir": "example-dist", "assets": [ "assets", "favicon.ico" diff --git a/circle.yml b/circle.yml index f6cf91ac6d..146a171107 100644 --- a/circle.yml +++ b/circle.yml @@ -16,6 +16,7 @@ dependencies: test: override: - yarn run ci + - yarn run example:test -- --watch=false deployment: builds: diff --git a/example-app/app/books/reducers/index.ts b/example-app/app/books/reducers/index.ts index c273977c6b..8800775d39 100644 --- a/example-app/app/books/reducers/index.ts +++ b/example-app/app/books/reducers/index.ts @@ -1,4 +1,9 @@ -import { createSelector, createFeatureSelector } from '@ngrx/store'; +import { InjectionToken } from '@angular/core'; +import { + createSelector, + createFeatureSelector, + ActionReducerMap, +} from '@ngrx/store'; import * as fromSearch from './search'; import * as fromBooks from './books'; import * as fromCollection from './collection'; diff --git a/example-app/app/reducers/index.ts b/example-app/app/reducers/index.ts index 1f04960303..08dbde4d90 100644 --- a/example-app/app/reducers/index.ts +++ b/example-app/app/reducers/index.ts @@ -4,6 +4,7 @@ import { createFeatureSelector, ActionReducer, } from '@ngrx/store'; +import { InjectionToken } from '@angular/core'; import { environment } from '../../environments/environment'; /** diff --git a/example-app/tsconfig.app.json b/example-app/tsconfig.app.json index 4f98f77aab..ee93846bfe 100644 --- a/example-app/tsconfig.app.json +++ b/example-app/tsconfig.app.json @@ -16,10 +16,10 @@ "baseUrl": ".", "rootDir": "../", "paths": { - "@ngrx/effects": ["../modules/effects"], - "@ngrx/store": ["../modules/store"], - "@ngrx/router-store": ["../modules/router-store"], - "@ngrx/store-devtools": ["../modules/store-devtools"] + "@ngrx/effects": ["../dist/effects"], + "@ngrx/store": ["../dist/store"], + "@ngrx/router-store": ["../dist/router-store"], + "@ngrx/store-devtools": ["../dist/store-devtools"] } }, "exclude": [ diff --git a/modules/effects/spec/effects_metadata.spec.ts b/modules/effects/spec/effects_metadata.spec.ts index 17bf474759..a77f3617fd 100644 --- a/modules/effects/spec/effects_metadata.spec.ts +++ b/modules/effects/spec/effects_metadata.spec.ts @@ -58,7 +58,7 @@ describe('Effect Metadata', () => { describe('getSourceProto', () => { it('should get the prototype for an instance of a source', () => { - class Fixture { } + class Fixture {} const instance = new Fixture(); const proto = getSourceForInstance(instance); diff --git a/modules/store/spec/modules.spec.ts b/modules/store/spec/modules.spec.ts index 798b63ee92..5a6690c691 100644 --- a/modules/store/spec/modules.spec.ts +++ b/modules/store/spec/modules.spec.ts @@ -22,6 +22,10 @@ describe(`Store Modules`, () => { 'Root Reducers' ); + const featureToken = new InjectionToken>( + 'Feature Reducers' + ); + // Trigger here is basically an action type used to trigger state update const createDummyReducer = (def: T, trigger: string): ActionReducer => ( s = def, @@ -126,11 +130,23 @@ describe(`Store Modules`, () => { }) class FeatureBModule {} + @NgModule({ + imports: [StoreModule.forFeature('c', featureToken)], + providers: [ + { + provide: featureToken, + useValue: featureBReducerMap, + }, + ], + }) + class FeatureCModule {} + @NgModule({ imports: [ StoreModule.forRoot(reducersToken), FeatureAModule, FeatureBModule, + FeatureCModule, ], providers: [ { @@ -158,6 +174,10 @@ describe(`Store Modules`, () => { list: [1, 2, 3], index: 2, }, + c: { + list: [1, 2, 3], + index: 2, + }, }); }); }); diff --git a/modules/store/src/index.ts b/modules/store/src/index.ts index d81d674b07..054c8576f6 100644 --- a/modules/store/src/index.ts +++ b/modules/store/src/index.ts @@ -26,13 +26,20 @@ export { INITIAL_STATE, _REDUCER_FACTORY, REDUCER_FACTORY, + _INITIAL_REDUCERS, INITIAL_REDUCERS, STORE_FEATURES, _INITIAL_STATE, META_REDUCERS, + _STORE_REDUCERS, + _FEATURE_REDUCERS, + FEATURE_REDUCERS, + _FEATURE_REDUCERS_TOKEN, } from './tokens'; export { StoreRootModule, StoreFeatureModule, _initialStateFactory, + _createStoreReducers, + _createFeatureReducers, } from './store_module'; diff --git a/modules/store/src/store_module.ts b/modules/store/src/store_module.ts index 0f09af89fc..0ccdecb411 100644 --- a/modules/store/src/store_module.ts +++ b/modules/store/src/store_module.ts @@ -4,6 +4,7 @@ import { ModuleWithProviders, OnDestroy, InjectionToken, + Optional, } from '@angular/core'; import { Action, @@ -17,11 +18,17 @@ import { compose, combineReducers, createReducerFactory } from './utils'; import { INITIAL_STATE, INITIAL_REDUCERS, + _INITIAL_REDUCERS, REDUCER_FACTORY, _REDUCER_FACTORY, STORE_FEATURES, _INITIAL_STATE, META_REDUCERS, + _STORE_REDUCERS, + FEATURE_REDUCERS, + _FEATURE_REDUCERS, + _FEATURE_REDUCERS_TOKEN, + _FEATURE_REDUCERS_DEFAULT, } from './tokens'; import { ACTIONS_SUBJECT_PROVIDERS, ActionsSubject } from './actions_subject'; import { @@ -49,13 +56,19 @@ export class StoreRootModule { export class StoreFeatureModule implements OnDestroy { constructor( @Inject(STORE_FEATURES) private features: StoreFeature[], + @Inject(FEATURE_REDUCERS) private featureReducers: ActionReducerMap[], private reducerManager: ReducerManager ) { features - .map(feature => { - return typeof feature.initialState === 'function' - ? { ...feature, initialState: feature.initialState() } - : feature; + .map((feature, index) => { + const featureReducerCollection = featureReducers.shift(); + const reducers = featureReducerCollection[index]; + + return { + ...feature, + reducers, + initialState: _initialStateFactory(feature.initialState), + }; }) .forEach(feature => reducerManager.addFeature(feature)); } @@ -94,9 +107,18 @@ export class StoreModule { useFactory: _initialStateFactory, deps: [_INITIAL_STATE], }, + { provide: _INITIAL_REDUCERS, useValue: reducers }, reducers instanceof InjectionToken - ? { provide: INITIAL_REDUCERS, useExisting: reducers } - : { provide: INITIAL_REDUCERS, useValue: reducers }, + ? [{ provide: _STORE_REDUCERS, useExisting: reducers }] + : [], + { + provide: INITIAL_REDUCERS, + deps: [ + _INITIAL_REDUCERS, + [new Optional(), new Inject(_STORE_REDUCERS)], + ], + useFactory: _createStoreReducers, + }, { provide: META_REDUCERS, useValue: config.metaReducers ? config.metaReducers : [], @@ -144,7 +166,6 @@ export class StoreModule { multi: true, useValue: >{ key: featureName, - reducers: reducers, reducerFactory: config.reducerFactory ? config.reducerFactory : combineReducers, @@ -152,11 +173,45 @@ export class StoreModule { initialState: config.initialState, }, }, + { provide: _FEATURE_REDUCERS, multi: true, useValue: reducers }, + { + provide: _FEATURE_REDUCERS_TOKEN, + multi: true, + useExisting: + reducers instanceof InjectionToken ? reducers : _FEATURE_REDUCERS, + }, + { + provide: FEATURE_REDUCERS, + multi: true, + deps: [ + _FEATURE_REDUCERS, + [new Optional(), new Inject(_FEATURE_REDUCERS_TOKEN)], + ], + useFactory: _createFeatureReducers, + }, ], }; } } +export function _createStoreReducers( + reducers: ActionReducerMap, + tokenReducers: ActionReducerMap +) { + return reducers instanceof InjectionToken ? tokenReducers : reducers; +} + +export function _createFeatureReducers( + reducerCollection: ActionReducerMap[], + tokenReducerCollection: ActionReducerMap[] +) { + return reducerCollection.map((reducer, index) => { + return reducer instanceof InjectionToken + ? tokenReducerCollection[index] + : reducer; + }); +} + export function _initialStateFactory(initialState: any): any { if (typeof initialState === 'function') { return initialState(); diff --git a/modules/store/src/tokens.ts b/modules/store/src/tokens.ts index 0e7b045887..9bf0fe3d4c 100644 --- a/modules/store/src/tokens.ts +++ b/modules/store/src/tokens.ts @@ -1,6 +1,8 @@ import { InjectionToken } from '@angular/core'; -export const _INITIAL_STATE = new InjectionToken('_ngrx/store Initial State'); +export const _INITIAL_STATE = new InjectionToken( + '@ngrx/store Internal Initial State' +); export const INITIAL_STATE = new InjectionToken('@ngrx/store Initial State'); export const REDUCER_FACTORY = new InjectionToken( '@ngrx/store Reducer Factory' @@ -11,5 +13,23 @@ export const _REDUCER_FACTORY = new InjectionToken( export const INITIAL_REDUCERS = new InjectionToken( '@ngrx/store Initial Reducers' ); +export const _INITIAL_REDUCERS = new InjectionToken( + '@ngrx/store Internal Initial Reducers' +); export const META_REDUCERS = new InjectionToken('@ngrx/store Meta Reducers'); export const STORE_FEATURES = new InjectionToken('@ngrx/store Store Features'); +export const _STORE_REDUCERS = new InjectionToken( + '@ngrx/store Internal Store Reducers' +); +export const _FEATURE_REDUCERS = new InjectionToken( + '@ngrx/store Internal Feature Reducers' +); +export const _FEATURE_REDUCERS_TOKEN = new InjectionToken( + '@ngrx/store Internal Feature Reducers Token' +); +export const _FEATURE_REDUCERS_DEFAULT = new InjectionToken( + '@ngrx/store Internal Feature Reducers Default' +); +export const FEATURE_REDUCERS = new InjectionToken( + '@ngrx/store Feature Reducers' +); diff --git a/package.json b/package.json index 79249ab005..41ae195008 100644 --- a/package.json +++ b/package.json @@ -10,12 +10,10 @@ "test:unit": "node ./tests.js", "test": "nyc yarn run test:unit", "clean": "git clean -xdf && yarn && yarn run bootstrap", - "clean:ng-cli-ts": "rimraf \"./node_modules/@angular/cli/node_modules/typescript\"", - "clean:ng-tools-ts": "rimraf \"./node_modules/@ngtools/webpack/node_modules/typescript\"", - "clean:ts": "yarn run clean:ng-cli-ts && yarn run clean:ng-tools-ts", "cli": "ng", "coverage:html": "nyc report --reporter=html", - "example:start": "yarn run cli -- serve", + "example:start": "yarn run build && yarn run cli -- serve", + "example:start:aot": "yarn run build && yarn run cli -- serve --aot", "example:test": "yarn run cli -- test --code-coverage", "ci": "yarn run build && yarn run test && nyc report --reporter=text-lcov | coveralls", "prettier": "prettier --parser typescript --single-quote --trailing-comma --write \"./**/*.ts\"",