Skip to content

Commit cdaeeb6

Browse files
fix(effects): run user provided effects defined as injection token (#3851)
Closes #3848
1 parent 34dd28c commit cdaeeb6

File tree

5 files changed

+74
-13
lines changed

5 files changed

+74
-13
lines changed

modules/effects/spec/integration.spec.ts

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { NgModule, Injectable } from '@angular/core';
1+
import { NgModule, Injectable, InjectionToken } from '@angular/core';
22
import { TestBed } from '@angular/core/testing';
33
import { RouterTestingModule } from '@angular/router/testing';
44
import { Router } from '@angular/router';
@@ -113,6 +113,34 @@ describe('NgRx Effects Integration spec', () => {
113113
expect(functionalEffectRun).toHaveBeenCalledTimes(2);
114114
});
115115

116+
it('runs user provided effects defined as injection token', () => {
117+
const userProvidedEffectRun = jest.fn<void, []>();
118+
119+
const TOKEN_EFFECTS = new InjectionToken('Token Effects', {
120+
providedIn: 'root',
121+
factory: () => ({
122+
userProvidedEffect$: createEffect(
123+
() => concat(of('ngrx'), NEVER).pipe(tap(userProvidedEffectRun)),
124+
{ dispatch: false }
125+
),
126+
}),
127+
});
128+
129+
TestBed.configureTestingModule({
130+
imports: [StoreModule.forRoot({}), EffectsModule.forRoot([])],
131+
providers: [
132+
{
133+
provide: USER_PROVIDED_EFFECTS,
134+
useValue: [TOKEN_EFFECTS],
135+
multi: true,
136+
},
137+
],
138+
});
139+
TestBed.inject(EffectSources);
140+
141+
expect(userProvidedEffectRun).toHaveBeenCalledTimes(1);
142+
});
143+
116144
describe('actions', () => {
117145
const createDispatchedReducer =
118146
(dispatchedActions: string[] = []) =>

modules/effects/spec/utils.spec.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@ import {
33
getSourceForInstance,
44
isClass,
55
isClassInstance,
6+
isToken,
67
} from '../src/utils';
8+
import { InjectionToken } from '@angular/core';
79

810
describe('getSourceForInstance', () => {
911
it('gets the prototype for an instance of a source', () => {
@@ -55,3 +57,17 @@ describe('getClasses', () => {
5557
expect(classes).toEqual([C1, C2, C3]);
5658
});
5759
});
60+
61+
describe('isToken', () => {
62+
it('returns true for a class', () => {
63+
expect(isToken(class C {})).toBe(true);
64+
});
65+
66+
it('returns true for an injection token', () => {
67+
expect(isToken(new InjectionToken('foo'))).toBe(true);
68+
});
69+
70+
it('returns false for a record', () => {
71+
expect(isToken({ foo: 'bar' })).toBe(false);
72+
});
73+
});

modules/effects/src/effects_module.ts

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,10 @@
1-
import { inject, ModuleWithProviders, NgModule, Type } from '@angular/core';
1+
import {
2+
inject,
3+
InjectionToken,
4+
ModuleWithProviders,
5+
NgModule,
6+
Type,
7+
} from '@angular/core';
28
import { EffectsFeatureModule } from './effects_feature_module';
39
import { EffectsRootModule } from './effects_root_module';
410
import { EffectsRunner } from './effects_runner';
@@ -11,7 +17,7 @@ import {
1117
USER_PROVIDED_EFFECTS,
1218
} from './tokens';
1319
import { FunctionalEffect } from './models';
14-
import { getClasses, isClass } from './utils';
20+
import { getClasses, isToken } from './utils';
1521

1622
@NgModule({})
1723
export class EffectsModule {
@@ -94,9 +100,11 @@ export class EffectsModule {
94100

95101
function createEffectsInstances(
96102
effectsGroups: Array<Type<unknown> | Record<string, FunctionalEffect>>[],
97-
userProvidedEffectsGroups: Type<unknown>[][]
103+
userProvidedEffectsGroups: Array<Type<unknown> | InjectionToken<unknown>>[]
98104
): unknown[] {
99-
const effects: Array<Type<unknown> | Record<string, FunctionalEffect>> = [];
105+
const effects: Array<
106+
Type<unknown> | Record<string, FunctionalEffect> | InjectionToken<unknown>
107+
> = [];
100108

101109
for (const effectsGroup of effectsGroups) {
102110
effects.push(...effectsGroup);
@@ -106,10 +114,10 @@ function createEffectsInstances(
106114
effects.push(...userProvidedEffectsGroup);
107115
}
108116

109-
return effects.map((effectsClassOrRecord) =>
110-
isClass(effectsClassOrRecord)
111-
? inject(effectsClassOrRecord)
112-
: effectsClassOrRecord
117+
return effects.map((effectsTokenOrRecord) =>
118+
isToken(effectsTokenOrRecord)
119+
? inject(effectsTokenOrRecord)
120+
: effectsTokenOrRecord
113121
);
114122
}
115123

modules/effects/src/tokens.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,9 @@ import { FunctionalEffect } from './models';
88
export const _ROOT_EFFECTS_GUARD = new InjectionToken<void>(
99
'@ngrx/effects Internal Root Guard'
1010
);
11-
export const USER_PROVIDED_EFFECTS = new InjectionToken<Type<unknown>[][]>(
12-
'@ngrx/effects User Provided Effects'
13-
);
11+
export const USER_PROVIDED_EFFECTS = new InjectionToken<
12+
Array<Type<unknown> | InjectionToken<unknown>>[]
13+
>('@ngrx/effects User Provided Effects');
1414
export const _ROOT_EFFECTS = new InjectionToken<
1515
[Array<Type<unknown> | Record<string, FunctionalEffect>>]
1616
>('@ngrx/effects Internal Root Effects');

modules/effects/src/utils.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Type } from '@angular/core';
1+
import { InjectionToken, Type } from '@angular/core';
22

33
export function getSourceForInstance<T>(instance: T): T {
44
return Object.getPrototypeOf(instance);
@@ -22,6 +22,15 @@ export function getClasses(
2222
return classesAndRecords.filter(isClass);
2323
}
2424

25+
export function isToken(
26+
tokenOrRecord:
27+
| Type<unknown>
28+
| InjectionToken<unknown>
29+
| Record<string, unknown>
30+
): tokenOrRecord is Type<unknown> | InjectionToken<unknown> {
31+
return tokenOrRecord instanceof InjectionToken || isClass(tokenOrRecord);
32+
}
33+
2534
// TODO: replace with RxJS interfaces when possible
2635
// needs dependency on RxJS >=7
2736
export interface NextNotification<T> {

0 commit comments

Comments
 (0)