Skip to content

Commit 069b12f

Browse files
MikeRyanDevbrandonroberts
authored andcommitted
feat(Store): Allow parent modules to provide reducers with tokens (#36)
Also allows feature modules to declare just one reducer function instead of a complete action reducer map. Closes #34
1 parent 3459bc5 commit 069b12f

File tree

7 files changed

+93
-34
lines changed

7 files changed

+93
-34
lines changed

modules/store/spec/modules.spec.ts

Lines changed: 59 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,73 @@
1-
import 'rxjs/add/operator/take';
2-
import { zip } from 'rxjs/observable/zip';
3-
import { ReflectiveInjector } from '@angular/core';
4-
import { createInjector, createChildInjector } from './helpers/injector';
5-
import { StoreModule, Store } from '../';
1+
import { TestBed } from '@angular/core/testing';
2+
import { NgModule, InjectionToken } from '@angular/core';
3+
import { StoreModule, Store, ActionReducer, ActionReducerMap } from '../';
64

75

86
describe('Nested Store Modules', () => {
9-
let store: Store<any>;
7+
type RootState = { fruit: string };
8+
type FeatureAState = number;
9+
type FeatureBState = { list: number[], index: number };
10+
type State = RootState & { a: FeatureAState } & { b: FeatureBState };
1011

11-
beforeEach(() => {
12-
const parentReducers = { stateKey: () => 'root' };
13-
const featureReducers = { stateKey: () => 'child' };
12+
let store: Store<State>;
13+
14+
const reducersToken = new InjectionToken<ActionReducerMap<RootState>>('Root Reducers');
15+
const rootFruitReducer: ActionReducer<string> = () => 'apple';
16+
const featureAReducer: ActionReducer<FeatureAState> = () => 5;
17+
const featureBListReducer: ActionReducer<number[]> = () => [1, 2, 3];
18+
const featureBIndexReducer: ActionReducer<number> = () => 2;
19+
const featureBReducerMap: ActionReducerMap<FeatureBState> = {
20+
list: featureBListReducer,
21+
index: featureBIndexReducer,
22+
};
23+
24+
@NgModule({
25+
imports: [
26+
StoreModule.forFeature('a', featureAReducer),
27+
]
28+
})
29+
class FeatureAModule { }
30+
31+
@NgModule({
32+
imports: [
33+
StoreModule.forFeature('b', featureBReducerMap),
34+
]
35+
})
36+
class FeatureBModule { }
1437

15-
const rootInjector = createInjector(StoreModule.forRoot(parentReducers));
16-
const featureInjector = createChildInjector(rootInjector, StoreModule.forFeature('inner', featureReducers));
38+
@NgModule({
39+
imports: [
40+
StoreModule.forRoot<RootState>(reducersToken),
41+
FeatureAModule,
42+
FeatureBModule,
43+
],
44+
providers: [
45+
{
46+
provide: reducersToken,
47+
useValue: { fruit: rootFruitReducer },
48+
}
49+
]
50+
})
51+
class RootModule { }
52+
53+
beforeEach(() => {
54+
TestBed.configureTestingModule({
55+
imports: [
56+
RootModule,
57+
]
58+
});
1759

18-
store = rootInjector.get(Store);
60+
store = TestBed.get(Store);
1961
});
2062

2163
it('should nest the child module in the root store object', () => {
2264
store.take(1).subscribe(state => {
2365
expect(state).toEqual({
24-
stateKey: 'root',
25-
inner: {
26-
stateKey: 'child'
66+
fruit: 'apple',
67+
a: 5,
68+
b: {
69+
list: [1, 2, 3],
70+
index: 2,
2771
}
2872
});
2973
});

modules/store/spec/ngc/main.ts

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -45,27 +45,33 @@ export const reducerToken = new InjectionToken('Reducers');
4545
})
4646
export class NgcSpecComponent {
4747
count: Observable<number>;
48-
constructor(public store:Store<AppState>){
48+
constructor(public store: Store<AppState>) {
4949
this.count = store.select(state => state.count);
5050
}
51-
increment(){
51+
increment() {
5252
this.store.dispatch({ type: INCREMENT });
5353
}
54-
decrement(){
54+
decrement() {
5555
this.store.dispatch({ type: DECREMENT });
5656
}
5757
}
5858

5959
@NgModule({
6060
imports: [
6161
BrowserModule,
62-
StoreModule.forRoot({ count: counterReducer }, {
63-
initialState: { count : 0 },
62+
StoreModule.forRoot(reducerToken, {
63+
initialState: { count: 0 },
6464
reducerFactory: combineReducers
6565
}),
6666
FeatureModule
6767
],
68+
providers: [
69+
{
70+
provide: reducerToken,
71+
useValue: { count: counterReducer }
72+
}
73+
],
6874
declarations: [NgcSpecComponent],
6975
bootstrap: [NgcSpecComponent]
7076
})
71-
export class NgcSpecModule {}
77+
export class NgcSpecModule { }

modules/store/src/models.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ export interface ActionReducerFactory<T, V extends Action = Action> {
1616

1717
export interface StoreFeature<T, V extends Action = Action> {
1818
key: string;
19-
reducers: ActionReducerMap<T, V>;
19+
reducers: ActionReducerMap<T, V> | ActionReducer<T, V>;
2020
reducerFactory: ActionReducerFactory<T, V>;
2121
initialState: T | undefined;
2222
}

modules/store/src/reducer_manager.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ export class ReducerManager extends BehaviorSubject<ActionReducer<any, any>> imp
2323
}
2424

2525
addFeature({ reducers, reducerFactory, initialState, key }: StoreFeature<any, any>) {
26-
const reducer = reducerFactory(reducers, initialState);
26+
const reducer = typeof reducers === 'function' ? reducers : reducerFactory(reducers, initialState);
2727

2828
this.addReducer(key, reducer);
2929
}

modules/store/src/selector.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,3 +125,7 @@ export function createFeatureSelector<T>(featureName: string): MemoizedSelector<
125125

126126
return Object.assign(memoized, { release: reset });
127127
}
128+
129+
export function isSelector(v: any): v is MemoizedSelector<any, any> {
130+
return typeof v === 'function' && v.release && typeof v.release === 'function';
131+
}

modules/store/src/store.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { Action, ActionReducer } from './models';
99
import { ActionsSubject } from './actions_subject';
1010
import { StateObservable } from './state';
1111
import { ReducerManager } from './reducer_manager';
12+
import { isSelector, createSelector } from './selector';
1213

1314

1415
@Injectable()
@@ -36,11 +37,14 @@ export class Store<T> extends Observable<Readonly<T>> implements Observer<Action
3637
if (typeof pathOrMapFn === 'string') {
3738
mapped$ = pluck.call(this, pathOrMapFn, ...paths);
3839
}
39-
else if (typeof pathOrMapFn === 'function') {
40+
else if (typeof pathOrMapFn === 'function' && isSelector(pathOrMapFn)) {
4041
mapped$ = map.call(this, pathOrMapFn);
4142
}
43+
else if (typeof pathOrMapFn === 'function') {
44+
mapped$ = map.call(this, createSelector(s => s, pathOrMapFn));
45+
}
4246
else {
43-
throw new TypeError(`Unexpected type '${ typeof pathOrMapFn }' in select operator,`
47+
throw new TypeError(`Unexpected type '${typeof pathOrMapFn}' in select operator,`
4448
+ ` expected 'string' or 'function'`);
4549
}
4650

modules/store/src/store_module.ts

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import { NgModule, Inject, ModuleWithProviders, OnDestroy } from '@angular/core';
2-
import { Action, ActionReducerMap, ActionReducerFactory, StoreFeature } from './models';
1+
import { NgModule, Inject, ModuleWithProviders, OnDestroy, InjectionToken } from '@angular/core';
2+
import { Action, ActionReducer, ActionReducerMap, ActionReducerFactory, StoreFeature } from './models';
33
import { combineReducers } from './utils';
44
import { INITIAL_STATE, INITIAL_REDUCERS, REDUCER_FACTORY, STORE_FEATURES } from './tokens';
55
import { ACTIONS_SUBJECT_PROVIDERS } from './actions_subject';
@@ -10,12 +10,12 @@ import { STORE_PROVIDERS } from './store';
1010

1111

1212

13-
@NgModule({ })
13+
@NgModule({})
1414
export class StoreRootModule {
1515

1616
}
1717

18-
@NgModule({ })
18+
@NgModule({})
1919
export class StoreFeatureModule implements OnDestroy {
2020
constructor(
2121
@Inject(STORE_FEATURES) private features: StoreFeature<any, any>[],
@@ -31,15 +31,15 @@ export class StoreFeatureModule implements OnDestroy {
3131

3232
export type StoreConfig<T, V extends Action = Action> = { initialState?: T, reducerFactory?: ActionReducerFactory<T, V> };
3333

34-
@NgModule({ })
34+
@NgModule({})
3535
export class StoreModule {
36-
static forRoot<T, V extends Action = Action>(reducers: ActionReducerMap<T, V>, config?: StoreConfig<T, V>): ModuleWithProviders;
37-
static forRoot(reducers: ActionReducerMap<any, any>, config: StoreConfig<any, any> = { }): ModuleWithProviders {
36+
static forRoot<T, V extends Action = Action>(reducers: ActionReducerMap<T, V> | InjectionToken<ActionReducerMap<T, V>>, config?: StoreConfig<T, V>): ModuleWithProviders;
37+
static forRoot(reducers: ActionReducerMap<any, any> | InjectionToken<ActionReducerMap<any, any>>, config: StoreConfig<any, any> = {}): ModuleWithProviders {
3838
return {
3939
ngModule: StoreRootModule,
4040
providers: [
4141
{ provide: INITIAL_STATE, useValue: config.initialState },
42-
{ provide: INITIAL_REDUCERS, useValue: reducers },
42+
reducers instanceof InjectionToken ? { provide: INITIAL_REDUCERS, useExisting: reducers } : { provide: INITIAL_REDUCERS, useValue: reducers },
4343
{ provide: REDUCER_FACTORY, useValue: config.reducerFactory ? config.reducerFactory : combineReducers },
4444
ACTIONS_SUBJECT_PROVIDERS,
4545
REDUCER_MANAGER_PROVIDERS,
@@ -51,7 +51,8 @@ export class StoreModule {
5151
}
5252

5353
static forFeature<T, V extends Action = Action>(featureName: string, reducers: ActionReducerMap<T, V>, config?: StoreConfig<T, V>): ModuleWithProviders;
54-
static forFeature(featureName: string, reducers: ActionReducerMap<any, any>, config: StoreConfig<any, any> = {}): ModuleWithProviders {
54+
static forFeature<T, V extends Action = Action>(featureName: string, reducer: ActionReducer<T, V>, config?: StoreConfig<T, V>): ModuleWithProviders;
55+
static forFeature(featureName: string, reducers: ActionReducerMap<any, any> | ActionReducer<any, any>, config: StoreConfig<any, any> = {}): ModuleWithProviders {
5556
return {
5657
ngModule: StoreFeatureModule,
5758
providers: [

0 commit comments

Comments
 (0)