Skip to content

Commit b46748c

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

File tree

4 files changed

+57
-4
lines changed

4 files changed

+57
-4
lines changed

modules/effects/spec/integration.spec.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,10 @@
1+
import { NgModuleFactoryLoader, NgModule } from '@angular/core';
12
import { TestBed } from '@angular/core/testing';
3+
import {
4+
RouterTestingModule,
5+
SpyNgModuleFactoryLoader,
6+
} from '@angular/router/testing';
7+
import { Router } from '@angular/router';
28
import { Store, Action } from '@ngrx/store';
39
import {
410
EffectsModule,
@@ -20,6 +26,7 @@ describe('NgRx Effects Integration spec', () => {
2026
RootEffectWithInitActionWithPayload,
2127
]),
2228
EffectsModule.forFeature([FeatEffectWithInitAction]),
29+
RouterTestingModule.withRoutes([]),
2330
],
2431
providers: [
2532
{
@@ -73,6 +80,21 @@ describe('NgRx Effects Integration spec', () => {
7380
]);
7481
});
7582

83+
it('throws if forRoot() is used more than once', (done: DoneFn) => {
84+
let router: Router = TestBed.get(Router);
85+
const loader: SpyNgModuleFactoryLoader = TestBed.get(NgModuleFactoryLoader);
86+
87+
loader.stubbedModules = { feature: FeatModuleWithForRoot };
88+
router.resetConfig([{ path: 'feature-path', loadChildren: 'feature' }]);
89+
90+
router.navigateByUrl('/feature-path').catch((err: TypeError) => {
91+
expect(err.message).toBe(
92+
'EffectsModule.forRoot() called twice. Feature modules should use EffectsModule.forFeature() instead.'
93+
);
94+
done();
95+
});
96+
});
97+
7698
class RootEffectWithInitAction implements OnInitEffects {
7799
ngrxOnInitEffects(): Action {
78100
return { type: '[RootEffectWithInitAction]: INIT' };
@@ -110,4 +132,9 @@ describe('NgRx Effects Integration spec', () => {
110132

111133
constructor(private effectIdentifier: string) {}
112134
}
135+
136+
@NgModule({
137+
imports: [EffectsModule.forRoot([])],
138+
})
139+
class FeatModuleWithForRoot {}
113140
});

modules/effects/src/effects_module.ts

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,13 @@
1-
import { NgModule, ModuleWithProviders, Type } from '@angular/core';
1+
import {
2+
NgModule,
3+
ModuleWithProviders,
4+
Type,
5+
Optional,
6+
SkipSelf,
7+
} from '@angular/core';
28
import { EffectSources } from './effect_sources';
39
import { Actions } from './actions';
4-
import { ROOT_EFFECTS, FEATURE_EFFECTS } from './tokens';
10+
import { ROOT_EFFECTS, FEATURE_EFFECTS, _ROOT_EFFECTS_GUARD } from './tokens';
511
import { EffectsFeatureModule } from './effects_feature_module';
612
import { EffectsRootModule } from './effects_root_module';
713
import { EffectsRunner } from './effects_runner';
@@ -31,6 +37,11 @@ export class EffectsModule {
3137
return {
3238
ngModule: EffectsRootModule,
3339
providers: [
40+
{
41+
provide: _ROOT_EFFECTS_GUARD,
42+
useFactory: _provideForRootGuard,
43+
deps: [[EffectsRunner, new Optional(), new SkipSelf()]],
44+
},
3445
EffectsRunner,
3546
EffectSources,
3647
Actions,
@@ -48,3 +59,12 @@ export class EffectsModule {
4859
export function createSourceInstances(...instances: any[]) {
4960
return instances;
5061
}
62+
63+
export function _provideForRootGuard(runner: EffectsRunner): any {
64+
if (runner) {
65+
throw new TypeError(
66+
`EffectsModule.forRoot() called twice. Feature modules should use EffectsModule.forFeature() instead.`
67+
);
68+
}
69+
return 'guarded';
70+
}

modules/effects/src/effects_root_module.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import {
77
} from '@ngrx/store';
88
import { EffectsRunner } from './effects_runner';
99
import { EffectSources } from './effect_sources';
10-
import { ROOT_EFFECTS } from './tokens';
10+
import { ROOT_EFFECTS, _ROOT_EFFECTS_GUARD } from './tokens';
1111

1212
export const ROOT_EFFECTS_INIT = '@ngrx/effects/init';
1313

@@ -19,7 +19,10 @@ export class EffectsRootModule {
1919
store: Store<any>,
2020
@Inject(ROOT_EFFECTS) rootEffects: any[],
2121
@Optional() storeRootModule: StoreRootModule,
22-
@Optional() storeFeatureModule: StoreFeatureModule
22+
@Optional() storeFeatureModule: StoreFeatureModule,
23+
@Optional()
24+
@Inject(_ROOT_EFFECTS_GUARD)
25+
guard: any
2326
) {
2427
runner.start();
2528

modules/effects/src/tokens.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
import { InjectionToken, Type } from '@angular/core';
22

3+
export const _ROOT_EFFECTS_GUARD = new InjectionToken<void>(
4+
'@ngrx/effects Internal Root Guard'
5+
);
36
export const IMMEDIATE_EFFECTS = new InjectionToken<any[]>(
47
'ngrx/effects: Immediate Effects'
58
);

0 commit comments

Comments
 (0)