Skip to content

Commit 5219ff5

Browse files
feat(effects): allow usage of empty forRoot array multiple times (#2774)
1 parent a489b48 commit 5219ff5

File tree

2 files changed

+50
-9
lines changed

2 files changed

+50
-9
lines changed

modules/effects/spec/integration.spec.ts

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import { mapTo, exhaustMap, tap } from 'rxjs/operators';
2020
import { Observable } from 'rxjs';
2121

2222
describe('NgRx Effects Integration spec', () => {
23-
it('throws if forRoot() is used more than once', (done: any) => {
23+
it('throws if forRoot() with Effects is used more than once', (done: any) => {
2424
TestBed.configureTestingModule({
2525
imports: [
2626
StoreModule.forRoot({}),
@@ -45,6 +45,30 @@ describe('NgRx Effects Integration spec', () => {
4545
});
4646
});
4747

48+
it('does not throw if forRoot() is used more than once with empty effects', (done: any) => {
49+
TestBed.configureTestingModule({
50+
imports: [
51+
StoreModule.forRoot({}),
52+
EffectsModule.forRoot([]),
53+
RouterTestingModule.withRoutes([]),
54+
],
55+
});
56+
57+
let router: Router = TestBed.inject(Router);
58+
const loader: SpyNgModuleFactoryLoader = TestBed.inject(
59+
NgModuleFactoryLoader
60+
) as SpyNgModuleFactoryLoader;
61+
62+
// empty forRoot([]) 👇
63+
loader.stubbedModules = { feature: FeatModuleWithEmptyForRoot };
64+
router.resetConfig([{ path: 'feature-path', loadChildren: 'feature' }]);
65+
66+
router.navigateByUrl('/feature-path').then(() => {
67+
// success
68+
done();
69+
});
70+
});
71+
4872
describe('actions', () => {
4973
const createDispatchedReducer = (dispatchedActions: string[] = []) => (
5074
state = {},
@@ -386,11 +410,19 @@ describe('NgRx Effects Integration spec', () => {
386410
}
387411
}
388412

413+
@Injectable()
414+
class SomeEffect {}
415+
389416
@NgModule({
390-
imports: [EffectsModule.forRoot()],
417+
imports: [EffectsModule.forRoot([SomeEffect])],
391418
})
392419
class FeatModuleWithForRoot {}
393420

421+
@NgModule({
422+
imports: [EffectsModule.forRoot([])],
423+
})
424+
class FeatModuleWithEmptyForRoot {}
425+
394426
@NgModule({
395427
imports: [
396428
EffectsModule.forFeature([FeatEffectFromLazyLoadedModuleWithInitAction]),

modules/effects/src/effects_module.ts

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import {
33
ModuleWithProviders,
44
NgModule,
55
Optional,
6+
Self,
67
SkipSelf,
78
Type,
89
} from '@angular/core';
@@ -57,11 +58,6 @@ export class EffectsModule {
5758
return {
5859
ngModule: EffectsRootModule,
5960
providers: [
60-
{
61-
provide: _ROOT_EFFECTS_GUARD,
62-
useFactory: _provideForRootGuard,
63-
deps: [[EffectsRunner, new Optional(), new SkipSelf()]],
64-
},
6561
{
6662
provide: EFFECTS_ERROR_HANDLER,
6763
useValue: defaultEffectsErrorHandler,
@@ -74,6 +70,14 @@ export class EffectsModule {
7470
provide: _ROOT_EFFECTS,
7571
useValue: [rootEffects],
7672
},
73+
{
74+
provide: _ROOT_EFFECTS_GUARD,
75+
useFactory: _provideForRootGuard,
76+
deps: [
77+
[EffectsRunner, new Optional(), new SkipSelf()],
78+
[_ROOT_EFFECTS, new Self()],
79+
],
80+
},
7781
{
7882
provide: USER_PROVIDED_EFFECTS,
7983
multi: true,
@@ -114,8 +118,13 @@ export function createEffectInstances(
114118
return effects.map((effect) => injector.get(effect));
115119
}
116120

117-
export function _provideForRootGuard(runner: EffectsRunner): any {
118-
if (runner) {
121+
export function _provideForRootGuard(
122+
runner: EffectsRunner,
123+
rootEffects: any[][]
124+
): any {
125+
// check whether any effects are actually passed
126+
const hasEffects = !(rootEffects.length === 1 && rootEffects[0].length === 0);
127+
if (hasEffects && runner) {
119128
throw new TypeError(
120129
`EffectsModule.forRoot() called twice. Feature modules should use EffectsModule.forFeature() instead.`
121130
);

0 commit comments

Comments
 (0)