Skip to content

Commit 96c5bdd

Browse files
feat(effects): remove @effect decorator (#3634)
Closes #3617 BREAKING CHANGES: The @effect decorator is removed BEFORE: Defining an effect is done with @effect @effect() data$ = this.actions$.pipe(); AFTER: Defining an effect is done with createEffect data$ = createEffect(() => this.actions$.pipe());
1 parent b9c1ab6 commit 96c5bdd

File tree

11 files changed

+73
-467
lines changed

11 files changed

+73
-467
lines changed

modules/effects/spec/effect_decorator.spec.ts

Lines changed: 0 additions & 45 deletions
This file was deleted.

modules/effects/spec/effect_sources.spec.ts

Lines changed: 0 additions & 246 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ import {
1313
import { map } from 'rxjs/operators';
1414

1515
import {
16-
Effect,
1716
EffectSources,
1817
OnIdentifyEffects,
1918
OnInitEffects,
@@ -76,251 +75,6 @@ describe('EffectSources', () => {
7675
return (effectSources as any)['toActions'].call(source);
7776
}
7877

79-
describe('with @Effect()', () => {
80-
const a = { type: 'From Source A' };
81-
const b = { type: 'From Source B' };
82-
const c = { type: 'From Source C that completes' };
83-
const d = { not: 'a valid action' };
84-
const e = undefined;
85-
const f = null;
86-
const i = { type: 'From Source Identifier' };
87-
const i2 = { type: 'From Source Identifier 2' };
88-
89-
const circularRef = {} as any;
90-
circularRef.circularRef = circularRef;
91-
const g = { circularRef };
92-
93-
const error = new Error('An Error');
94-
95-
class SourceA {
96-
@Effect() a$ = alwaysOf(a);
97-
}
98-
99-
class SourceB {
100-
@Effect() b$ = alwaysOf(b);
101-
}
102-
103-
class SourceC {
104-
@Effect() c$ = of(c);
105-
}
106-
107-
class SourceD {
108-
@Effect() d$ = alwaysOf(d);
109-
}
110-
111-
class SourceE {
112-
@Effect() e$ = alwaysOf(e);
113-
}
114-
115-
class SourceF {
116-
@Effect() f$ = alwaysOf(f);
117-
}
118-
119-
class SourceG {
120-
@Effect() g$ = alwaysOf(g);
121-
}
122-
123-
class SourceError {
124-
@Effect() e$ = throwError(() => error);
125-
}
126-
127-
class SourceH {
128-
@Effect() empty = of('value');
129-
@Effect()
130-
never = timer(50, getTestScheduler() as any).pipe(map(() => 'update'));
131-
}
132-
133-
class SourceWithIdentifier implements OnIdentifyEffects {
134-
effectIdentifier: string;
135-
@Effect() i$ = alwaysOf(i);
136-
137-
ngrxOnIdentifyEffects() {
138-
return this.effectIdentifier;
139-
}
140-
141-
constructor(identifier: string) {
142-
this.effectIdentifier = identifier;
143-
}
144-
}
145-
146-
class SourceWithIdentifier2 implements OnIdentifyEffects {
147-
effectIdentifier: string;
148-
@Effect() i2$ = alwaysOf(i2);
149-
150-
ngrxOnIdentifyEffects() {
151-
return this.effectIdentifier;
152-
}
153-
154-
constructor(identifier: string) {
155-
this.effectIdentifier = identifier;
156-
}
157-
}
158-
159-
it('should resolve effects from instances', () => {
160-
const sources$ = cold('--a--', { a: new SourceA() });
161-
const expected = cold('--a--', { a });
162-
163-
const output = toActions(sources$);
164-
165-
expect(output).toBeObservable(expected);
166-
});
167-
168-
it('should ignore duplicate sources', () => {
169-
const sources$ = cold('--a--a--a--', {
170-
a: new SourceA(),
171-
});
172-
const expected = cold('--a--------', { a });
173-
174-
const output = toActions(sources$);
175-
176-
expect(output).toBeObservable(expected);
177-
});
178-
179-
it('should resolve effects with different identifiers', () => {
180-
const sources$ = cold('--a--b--c--', {
181-
a: new SourceWithIdentifier('a'),
182-
b: new SourceWithIdentifier('b'),
183-
c: new SourceWithIdentifier('c'),
184-
});
185-
const expected = cold('--i--i--i--', { i });
186-
187-
const output = toActions(sources$);
188-
189-
expect(output).toBeObservable(expected);
190-
});
191-
192-
it('should ignore effects with the same identifier', () => {
193-
const sources$ = cold('--a--b--c--', {
194-
a: new SourceWithIdentifier('a'),
195-
b: new SourceWithIdentifier('a'),
196-
c: new SourceWithIdentifier('a'),
197-
});
198-
const expected = cold('--i--------', { i });
199-
200-
const output = toActions(sources$);
201-
202-
expect(output).toBeObservable(expected);
203-
});
204-
205-
it('should resolve effects with same identifiers but different classes', () => {
206-
const sources$ = cold('--a--b--c--d--', {
207-
a: new SourceWithIdentifier('a'),
208-
b: new SourceWithIdentifier2('a'),
209-
c: new SourceWithIdentifier('b'),
210-
d: new SourceWithIdentifier2('b'),
211-
});
212-
const expected = cold('--a--b--a--b--', { a: i, b: i2 });
213-
214-
const output = toActions(sources$);
215-
216-
expect(output).toBeObservable(expected);
217-
});
218-
219-
it('should report an error if an effect dispatches an invalid action', () => {
220-
const sources$ = of(new SourceD());
221-
222-
toActions(sources$).subscribe();
223-
224-
expect(mockErrorReporter.handleError).toHaveBeenCalledWith(
225-
new Error(
226-
'Effect "SourceD.d$" dispatched an invalid action: {"not":"a valid action"}'
227-
)
228-
);
229-
});
230-
231-
it('should report an error if an effect dispatches an `undefined`', () => {
232-
const sources$ = of(new SourceE());
233-
234-
toActions(sources$).subscribe();
235-
236-
expect(mockErrorReporter.handleError).toHaveBeenCalledWith(
237-
new Error(
238-
'Effect "SourceE.e$" dispatched an invalid action: undefined'
239-
)
240-
);
241-
});
242-
243-
it('should report an error if an effect dispatches a `null`', () => {
244-
const sources$ = of(new SourceF());
245-
246-
toActions(sources$).subscribe();
247-
248-
expect(mockErrorReporter.handleError).toHaveBeenCalledWith(
249-
new Error('Effect "SourceF.f$" dispatched an invalid action: null')
250-
);
251-
});
252-
253-
it('should report an error if an effect throws one', () => {
254-
const sources$ = of(new SourceError());
255-
256-
toActions(sources$).subscribe();
257-
258-
expect(mockErrorReporter.handleError).toHaveBeenCalledWith(
259-
new Error('An Error')
260-
);
261-
});
262-
263-
it('should resubscribe on error by default', () => {
264-
class Eff {
265-
@Effect()
266-
b$ = hot('a--e--b--e--c--e--d').pipe(
267-
map((v) => {
268-
if (v == 'e') throw new Error('An Error');
269-
return v;
270-
})
271-
);
272-
}
273-
274-
const sources$ = of(new Eff());
275-
276-
// 👇 'e' is ignored.
277-
const expected = cold('a-----b-----c-----d');
278-
expect(toActions(sources$)).toBeObservable(expected);
279-
});
280-
281-
it('should not resubscribe on error when useEffectsErrorHandler is false', () => {
282-
class Eff {
283-
@Effect({ useEffectsErrorHandler: false })
284-
b$ = hot('a--b--c--d').pipe(
285-
map((v) => {
286-
if (v == 'b') throw new Error('An Error');
287-
return v;
288-
})
289-
);
290-
}
291-
292-
const sources$ = of(new Eff());
293-
294-
// 👇 completes.
295-
const expected = cold('a--|');
296-
297-
expect(toActions(sources$)).toBeObservable(expected);
298-
});
299-
300-
it(`should not break when the action in the error message can't be stringified`, () => {
301-
const sources$ = of(new SourceG());
302-
303-
toActions(sources$).subscribe();
304-
305-
expect(mockErrorReporter.handleError).toHaveBeenCalledWith(
306-
new Error(
307-
'Effect "SourceG.g$" dispatched an invalid action: [object Object]'
308-
)
309-
);
310-
});
311-
312-
it('should not complete the group if just one effect completes', () => {
313-
const sources$ = cold('g', {
314-
g: new SourceH(),
315-
});
316-
const expected = cold('a----b-----', { a: 'value', b: 'update' });
317-
318-
const output = toActions(sources$);
319-
320-
expect(output).toBeObservable(expected);
321-
});
322-
});
323-
32478
describe('with createEffect()', () => {
32579
const a = { type: 'From Source A' };
32680
const b = { type: 'From Source B' };

modules/effects/spec/effects_feature_module.spec.ts

Lines changed: 1 addition & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import {
99
StoreModule,
1010
} from '@ngrx/store';
1111
import { map, withLatestFrom } from 'rxjs/operators';
12-
import { Actions, Effect, EffectsModule, ofType, createEffect } from '../';
12+
import { Actions, EffectsModule, ofType, createEffect } from '../';
1313
import { EffectsFeatureModule } from '../src/effects_feature_module';
1414
import { EffectsRootModule } from '../src/effects_root_module';
1515
import { FEATURE_EFFECTS } from '../src/tokens';
@@ -67,22 +67,6 @@ describe('Effects Feature Module', () => {
6767
store = TestBed.inject(Store);
6868
});
6969

70-
it('should have the feature state defined to select from the effect', (done: any) => {
71-
const action = { type: 'INCREMENT' };
72-
const result = { type: 'INCREASE' };
73-
74-
effects.effectWithStore.subscribe((res) => {
75-
expect(res).toEqual(result);
76-
});
77-
78-
store.dispatch(action);
79-
80-
store.pipe(select(getDataState)).subscribe((data) => {
81-
expect(data).toBe(110);
82-
done();
83-
});
84-
});
85-
8670
it('should have the feature state defined to select from the createEffect', (done: any) => {
8771
const action = { type: 'CREATE_INCREMENT' };
8872
const result = { type: 'CREATE_INCREASE' };
@@ -145,13 +129,6 @@ const getCreateDataState = createSelector(
145129
class FeatureEffects {
146130
constructor(private actions: Actions, private store: Store<State>) {}
147131

148-
@Effect()
149-
effectWithStore = this.actions.pipe(
150-
ofType('INCREMENT'),
151-
withLatestFrom(this.store.select(getDataState)),
152-
map(([action, state]) => ({ type: 'INCREASE' }))
153-
);
154-
155132
createEffectWithStore = createEffect(() =>
156133
this.actions.pipe(
157134
ofType('CREATE_INCREMENT'),

0 commit comments

Comments
 (0)