Skip to content

Commit 4304865

Browse files
timdeschryverbrandonroberts
authored andcommitted
feat(store): throw error when forRoot() is used more than once
1 parent cb74051 commit 4304865

3 files changed

Lines changed: 57 additions & 2 deletions

File tree

modules/store/spec/integration.spec.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,12 @@ import {
2121
visibilityFilter,
2222
VisibilityFilters,
2323
} from './fixtures/todos';
24+
import {
25+
RouterTestingModule,
26+
SpyNgModuleFactoryLoader,
27+
} from '@angular/router/testing';
28+
import { Component, NgModuleFactoryLoader, NgModule } from '@angular/core';
29+
import { Router } from '@angular/router';
2430

2531
interface Todo {
2632
id: number;
@@ -478,5 +484,31 @@ describe('ngRx Integration spec', () => {
478484
expect(state).toEqual(expected);
479485
});
480486
});
487+
488+
it('throws if forRoot() is used more than once', (done: DoneFn) => {
489+
@NgModule({
490+
imports: [StoreModule.forRoot({})],
491+
})
492+
class FeatureModule {}
493+
494+
TestBed.configureTestingModule({
495+
imports: [StoreModule.forRoot({}), RouterTestingModule.withRoutes([])],
496+
});
497+
498+
let router: Router = TestBed.get(Router);
499+
const loader: SpyNgModuleFactoryLoader = TestBed.get(
500+
NgModuleFactoryLoader
501+
);
502+
503+
loader.stubbedModules = { feature: FeatureModule };
504+
router.resetConfig([{ path: 'feature-path', loadChildren: 'feature' }]);
505+
506+
router.navigateByUrl('/feature-path').catch((err: TypeError) => {
507+
expect(err.message).toBe(
508+
'StoreModule.forRoot() called twice. Feature modules should use StoreModule.forFeature() instead.'
509+
);
510+
done();
511+
});
512+
});
481513
});
482514
});

modules/store/src/store_module.ts

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import {
55
OnDestroy,
66
InjectionToken,
77
Injector,
8+
Optional,
9+
SkipSelf,
810
} from '@angular/core';
911
import {
1012
Action,
@@ -16,7 +18,7 @@ import {
1618
MetaReducer,
1719
RuntimeChecks,
1820
} from './models';
19-
import { compose, combineReducers, createReducerFactory } from './utils';
21+
import { combineReducers, createReducerFactory } from './utils';
2022
import {
2123
INITIAL_STATE,
2224
INITIAL_REDUCERS,
@@ -34,6 +36,7 @@ import {
3436
_FEATURE_CONFIGS,
3537
USER_PROVIDED_META_REDUCERS,
3638
_RESOLVED_META_REDUCERS,
39+
_ROOT_STORE_GUARD,
3740
} from './tokens';
3841
import { ACTIONS_SUBJECT_PROVIDERS, ActionsSubject } from './actions_subject';
3942
import {
@@ -55,7 +58,10 @@ export class StoreRootModule {
5558
actions$: ActionsSubject,
5659
reducer$: ReducerObservable,
5760
scannedActions$: ScannedActionsSubject,
58-
store: Store<any>
61+
store: Store<any>,
62+
@Optional()
63+
@Inject(_ROOT_STORE_GUARD)
64+
guard: any
5965
) {}
6066
}
6167

@@ -112,6 +118,11 @@ export class StoreModule {
112118
return {
113119
ngModule: StoreRootModule,
114120
providers: [
121+
{
122+
provide: _ROOT_STORE_GUARD,
123+
useFactory: _provideForRootGuard,
124+
deps: [[Store, new Optional(), new SkipSelf()]],
125+
},
115126
{ provide: _INITIAL_STATE, useValue: config.initialState },
116127
{
117128
provide: INITIAL_STATE,
@@ -285,3 +296,12 @@ export function _concatMetaReducers(
285296
): MetaReducer[] {
286297
return metaReducers.concat(userProvidedMetaReducers);
287298
}
299+
300+
export function _provideForRootGuard(store: Store<any>): any {
301+
if (store) {
302+
throw new TypeError(
303+
`StoreModule.forRoot() called twice. Feature modules should use StoreModule.forFeature() instead.`
304+
);
305+
}
306+
return 'guarded';
307+
}

modules/store/src/tokens.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
import { InjectionToken } from '@angular/core';
22
import { RuntimeChecks, MetaReducer } from './models';
33

4+
export const _ROOT_STORE_GUARD = new InjectionToken<void>(
5+
'@ngrx/store Internal Root Guard'
6+
);
47
export const _INITIAL_STATE = new InjectionToken(
58
'@ngrx/store Internal Initial State'
69
);

0 commit comments

Comments
 (0)