Skip to content

Commit 15a4b58

Browse files
timdeschryverbrandonroberts
authored andcommitted
feat(effects): dispatch feature effects action on init (#1305)
Closes #683
1 parent fa21f29 commit 15a4b58

File tree

4 files changed

+117
-20
lines changed

4 files changed

+117
-20
lines changed

docs/effects/api.md

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ You can see this action as a lifecycle hook, which you can use in order to execu
2626
@Effect()
2727
init$ = this.actions$.pipe(
2828
ofType(ROOT_EFFECTS_INIT),
29-
map(_ => ...)
29+
map(action => ...)
3030
);
3131
```
3232

@@ -45,6 +45,31 @@ Usage:
4545
export class FeatureModule {}
4646
```
4747

48+
### UPDATE_EFFECTS
49+
50+
After feature effects are registered, an `UPDATE_EFFECTS` action is dispatched.
51+
52+
```ts
53+
type UpdateEffects = {
54+
type: typeof UPDATE_EFFECTS;
55+
effects: string[];
56+
};
57+
```
58+
59+
For example, when you register your feature module as `EffectsModule.forFeature([SomeEffectsClass, AnotherEffectsClass])`,
60+
it has `SomeEffectsClass` and `AnotherEffectsClass` in an array as its payload.
61+
62+
To dispatch an action when the `SomeEffectsClass` effect has been registered, listen to the `UPDATE_EFFECTS` action and use the `effects` payload to filter out non-important effects.
63+
64+
```ts
65+
@Effect()
66+
init = this.actions.pipe(
67+
ofType<UpdateEffects>(UPDATE_EFFECTS)
68+
filter(action => action.effects.includes('SomeEffectsClass')),
69+
map(action => ...)
70+
);
71+
```
72+
4873
## Actions
4974

5075
Stream of all actions dispatched in your application including actions dispatched by effect streams.

modules/effects/spec/effects_feature_module.spec.ts

Lines changed: 65 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { Injectable, NgModule } from '@angular/core';
22
import { TestBed } from '@angular/core/testing';
3+
import { combineLatest } from 'rxjs';
34
import {
45
Action,
56
createFeatureSelector,
@@ -8,25 +9,39 @@ import {
89
Store,
910
StoreModule,
1011
} from '@ngrx/store';
11-
import { Observable } from 'rxjs';
12-
import { map, withLatestFrom } from 'rxjs/operators';
13-
14-
import { Actions, Effect, EffectsModule } from '../';
15-
import { EffectsFeatureModule } from '../src/effects_feature_module';
12+
import { map, withLatestFrom, filter } from 'rxjs/operators';
13+
import { Actions, Effect, EffectsModule, ofType } from '../';
14+
import {
15+
EffectsFeatureModule,
16+
UPDATE_EFFECTS,
17+
UpdateEffects,
18+
} from '../src/effects_feature_module';
1619
import { EffectsRootModule } from '../src/effects_root_module';
1720
import { FEATURE_EFFECTS } from '../src/tokens';
1821

1922
describe('Effects Feature Module', () => {
2023
describe('when registered', () => {
21-
const sourceA = 'sourceA';
22-
const sourceB = 'sourceB';
23-
const sourceC = 'sourceC';
24-
const effectSourceGroups = [[sourceA], [sourceB], [sourceC]];
24+
class SourceA {}
25+
class SourceB {}
26+
class SourceC {}
27+
28+
const sourceA = new SourceA();
29+
const sourceB = new SourceB();
30+
const sourceC = new SourceC();
31+
32+
const effectSourceGroups = [[sourceA], [sourceB, sourceC]];
2533
let mockEffectSources: { addEffects: jasmine.Spy };
34+
let mockStore: { dispatch: jasmine.Spy };
2635

2736
beforeEach(() => {
2837
TestBed.configureTestingModule({
2938
providers: [
39+
{
40+
provide: Store,
41+
useValue: {
42+
dispatch: jasmine.createSpy('dispatch'),
43+
},
44+
},
3045
{
3146
provide: EffectsRootModule,
3247
useValue: {
@@ -42,6 +57,7 @@ describe('Effects Feature Module', () => {
4257
});
4358

4459
mockEffectSources = TestBed.get(EffectsRootModule);
60+
mockStore = TestBed.get(Store);
4561
});
4662

4763
it('should add all effects when instantiated', () => {
@@ -51,11 +67,24 @@ describe('Effects Feature Module', () => {
5167
expect(mockEffectSources.addEffects).toHaveBeenCalledWith(sourceB);
5268
expect(mockEffectSources.addEffects).toHaveBeenCalledWith(sourceC);
5369
});
70+
71+
it('should dispatch update-effects actions when instantiated', () => {
72+
TestBed.get(EffectsFeatureModule);
73+
74+
expect(mockStore.dispatch).toHaveBeenCalledWith({
75+
type: UPDATE_EFFECTS,
76+
effects: ['SourceA'],
77+
});
78+
79+
expect(mockStore.dispatch).toHaveBeenCalledWith({
80+
type: UPDATE_EFFECTS,
81+
effects: ['SourceB', 'SourceC'],
82+
});
83+
});
5484
});
5585

5686
describe('when registered in a different NgModule from the feature state', () => {
5787
let effects: FeatureEffects;
58-
let actions$: Observable<any>;
5988
let store: Store<any>;
6089

6190
beforeEach(() => {
@@ -77,8 +106,12 @@ describe('Effects Feature Module', () => {
77106

78107
store.dispatch(action);
79108

80-
store.pipe(select(getDataState)).subscribe(res => {
81-
expect(res).toBe(110);
109+
combineLatest(
110+
store.pipe(select(getDataState)),
111+
store.pipe(select(getInitialized))
112+
).subscribe(([data, initialized]) => {
113+
expect(data).toBe(110);
114+
expect(initialized).toBe(true);
82115
done();
83116
});
84117
});
@@ -93,16 +126,25 @@ interface State {
93126

94127
interface DataState {
95128
data: number;
129+
initialized: boolean;
96130
}
97131

98132
const initialState: DataState = {
99133
data: 100,
134+
initialized: false,
100135
};
101136

102137
function reducer(state: DataState = initialState, action: Action) {
103138
switch (action.type) {
139+
case 'INITIALIZE_FEATURE': {
140+
return {
141+
...state,
142+
initialized: true,
143+
};
144+
}
104145
case 'INCREASE':
105146
return {
147+
...state,
106148
data: state.data + 10,
107149
};
108150
}
@@ -112,11 +154,22 @@ function reducer(state: DataState = initialState, action: Action) {
112154
const getFeatureState = createFeatureSelector<DataState>(FEATURE_KEY);
113155

114156
const getDataState = createSelector(getFeatureState, state => state.data);
157+
const getInitialized = createSelector(
158+
getFeatureState,
159+
state => state.initialized
160+
);
115161

116162
@Injectable()
117163
class FeatureEffects {
118164
constructor(private actions: Actions, private store: Store<State>) {}
119165

166+
@Effect()
167+
init = this.actions.pipe(
168+
ofType<UpdateEffects>(UPDATE_EFFECTS),
169+
filter(action => action.effects.includes('FeatureEffects')),
170+
map(action => ({ type: 'INITIALIZE_FEATURE' }))
171+
);
172+
120173
@Effect()
121174
effectWithStore = this.actions.ofType('INCREMENT').pipe(
122175
withLatestFrom(this.store.select(getDataState)),
Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,38 @@
11
import { NgModule, Inject, Optional } from '@angular/core';
2-
import { StoreRootModule, StoreFeatureModule } from '@ngrx/store';
2+
import { StoreRootModule, StoreFeatureModule, Store } from '@ngrx/store';
33
import { EffectsRootModule } from './effects_root_module';
44
import { FEATURE_EFFECTS } from './tokens';
5+
import { getSourceForInstance } from './effects_metadata';
6+
7+
export const UPDATE_EFFECTS = '@ngrx/effects/update-effects';
8+
export type UpdateEffects = {
9+
type: typeof UPDATE_EFFECTS;
10+
effects: string[];
11+
};
512

613
@NgModule({})
714
export class EffectsFeatureModule {
815
constructor(
9-
private root: EffectsRootModule,
16+
root: EffectsRootModule,
17+
store: Store<any>,
1018
@Inject(FEATURE_EFFECTS) effectSourceGroups: any[][],
1119
@Optional() storeRootModule: StoreRootModule,
1220
@Optional() storeFeatureModule: StoreFeatureModule
1321
) {
14-
effectSourceGroups.forEach(group =>
15-
group.forEach(effectSourceInstance =>
16-
root.addEffects(effectSourceInstance)
17-
)
18-
);
22+
effectSourceGroups.forEach(group => {
23+
let effectSourceNames: string[] = [];
24+
25+
group.forEach(effectSourceInstance => {
26+
root.addEffects(effectSourceInstance);
27+
28+
const { constructor } = getSourceForInstance(effectSourceInstance);
29+
effectSourceNames.push(constructor.name);
30+
});
31+
32+
store.dispatch(<UpdateEffects>{
33+
type: UPDATE_EFFECTS,
34+
effects: effectSourceNames,
35+
});
36+
});
1937
}
2038
}

modules/effects/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,4 @@ export { EffectSources } from './effect_sources';
1010
export { OnRunEffects } from './on_run_effects';
1111
export { EffectNotification } from './effect_notification';
1212
export { ROOT_EFFECTS_INIT } from './effects_root_module';
13+
export { UPDATE_EFFECTS, UpdateEffects } from './effects_feature_module';

0 commit comments

Comments
 (0)