diff --git a/modules/effects/spec/integration.spec.ts b/modules/effects/spec/integration.spec.ts index e47fbaabe7..fe58788a17 100644 --- a/modules/effects/spec/integration.spec.ts +++ b/modules/effects/spec/integration.spec.ts @@ -1,4 +1,10 @@ +import { NgModuleFactoryLoader, NgModule } from '@angular/core'; import { TestBed } from '@angular/core/testing'; +import { + RouterTestingModule, + SpyNgModuleFactoryLoader, +} from '@angular/router/testing'; +import { Router } from '@angular/router'; import { Store, Action } from '@ngrx/store'; import { EffectsModule, @@ -20,6 +26,7 @@ describe('NgRx Effects Integration spec', () => { RootEffectWithInitActionWithPayload, ]), EffectsModule.forFeature([FeatEffectWithInitAction]), + RouterTestingModule.withRoutes([]), ], providers: [ { @@ -73,6 +80,21 @@ describe('NgRx Effects Integration spec', () => { ]); }); + it('throws if forRoot() is used more than once', (done: DoneFn) => { + let router: Router = TestBed.get(Router); + const loader: SpyNgModuleFactoryLoader = TestBed.get(NgModuleFactoryLoader); + + loader.stubbedModules = { feature: FeatModuleWithForRoot }; + router.resetConfig([{ path: 'feature-path', loadChildren: 'feature' }]); + + router.navigateByUrl('/feature-path').catch((err: TypeError) => { + expect(err.message).toBe( + 'EffectsModule.forRoot() called twice. Feature modules should use EffectsModule.forFeature() instead.' + ); + done(); + }); + }); + class RootEffectWithInitAction implements OnInitEffects { ngrxOnInitEffects(): Action { return { type: '[RootEffectWithInitAction]: INIT' }; @@ -110,4 +132,9 @@ describe('NgRx Effects Integration spec', () => { constructor(private effectIdentifier: string) {} } + + @NgModule({ + imports: [EffectsModule.forRoot([])], + }) + class FeatModuleWithForRoot {} }); diff --git a/modules/effects/src/effects_module.ts b/modules/effects/src/effects_module.ts index e7470cf841..187f1dc753 100644 --- a/modules/effects/src/effects_module.ts +++ b/modules/effects/src/effects_module.ts @@ -1,7 +1,13 @@ -import { NgModule, ModuleWithProviders, Type } from '@angular/core'; +import { + NgModule, + ModuleWithProviders, + Type, + Optional, + SkipSelf, +} from '@angular/core'; import { EffectSources } from './effect_sources'; import { Actions } from './actions'; -import { ROOT_EFFECTS, FEATURE_EFFECTS } from './tokens'; +import { ROOT_EFFECTS, FEATURE_EFFECTS, _ROOT_EFFECTS_GUARD } from './tokens'; import { EffectsFeatureModule } from './effects_feature_module'; import { EffectsRootModule } from './effects_root_module'; import { EffectsRunner } from './effects_runner'; @@ -31,6 +37,11 @@ export class EffectsModule { return { ngModule: EffectsRootModule, providers: [ + { + provide: _ROOT_EFFECTS_GUARD, + useFactory: _provideForRootGuard, + deps: [[EffectsRunner, new Optional(), new SkipSelf()]], + }, EffectsRunner, EffectSources, Actions, @@ -48,3 +59,12 @@ export class EffectsModule { export function createSourceInstances(...instances: any[]) { return instances; } + +export function _provideForRootGuard(runner: EffectsRunner): any { + if (runner) { + throw new TypeError( + `EffectsModule.forRoot() called twice. Feature modules should use EffectsModule.forFeature() instead.` + ); + } + return 'guarded'; +} diff --git a/modules/effects/src/effects_root_module.ts b/modules/effects/src/effects_root_module.ts index dfd5dd3681..1bf4dc219b 100644 --- a/modules/effects/src/effects_root_module.ts +++ b/modules/effects/src/effects_root_module.ts @@ -7,7 +7,7 @@ import { } from '@ngrx/store'; import { EffectsRunner } from './effects_runner'; import { EffectSources } from './effect_sources'; -import { ROOT_EFFECTS } from './tokens'; +import { ROOT_EFFECTS, _ROOT_EFFECTS_GUARD } from './tokens'; export const ROOT_EFFECTS_INIT = '@ngrx/effects/init'; @@ -19,7 +19,10 @@ export class EffectsRootModule { store: Store, @Inject(ROOT_EFFECTS) rootEffects: any[], @Optional() storeRootModule: StoreRootModule, - @Optional() storeFeatureModule: StoreFeatureModule + @Optional() storeFeatureModule: StoreFeatureModule, + @Optional() + @Inject(_ROOT_EFFECTS_GUARD) + guard: any ) { runner.start(); diff --git a/modules/effects/src/tokens.ts b/modules/effects/src/tokens.ts index b4bc66d18d..de3923be1e 100644 --- a/modules/effects/src/tokens.ts +++ b/modules/effects/src/tokens.ts @@ -1,5 +1,8 @@ import { InjectionToken, Type } from '@angular/core'; +export const _ROOT_EFFECTS_GUARD = new InjectionToken( + '@ngrx/effects Internal Root Guard' +); export const IMMEDIATE_EFFECTS = new InjectionToken( 'ngrx/effects: Immediate Effects' );