Is it possible to wait for two actions like Promise.all in NgRx? For example:
@Effect()
public addUser() {
return this.actions$.ofType(user.ADD)
.switchMap(() => {
return this.userService.add();
})
.map(() => {
return new user.AddSuccessAction();
});
}
@Effect()
public addUserOptions() {
return this.actions$.ofType(userOptions.ADD)
.switchMap(() => {
return this.userOptionsService.add();
})
.map(() => {
return new userOptions.AddSuccessAction();
});
}
@Effect()
public complete() {
return this.actions$.ofType(user.ADD_SUCCESS, userOptions.ADD_SUCCESS)
// how to mimic the behavior of Promise.all here?
.switchMap(() => {
return this.statisticService.add();
})
.map(() => {
return new account.CompleteAction();
});
}
UPDATED My goal is to achieve similar functionality to Promise.all. How can I trigger two effects concurrently, wait for them to finish, and then dispatch a third action? Something like . With promises, it was straightforward:
Promise.all([fetch1, fetch2]).then(fetch3);
Is this possible with NgRx/effects? Or am I approaching this the wrong way?
ANSWER
There are several options you can explore:
1) Avoid using generic actions.
Follow the guidelines from Myke Ryan's presentation: https://youtu.be/JmnsEvoy-gY
Pros: easier debugging process
Cons: requires lots of boilerplate code and actions
2) Utilize a complex stream with nested actions.
Refer to this article:
Here's a simple example handling two actions concurrently:
@Effect()
public someAction(): Observable<Action> {
return this.actions$.pipe(
ofType(actions.SOME_ACTION),
map((action: actions.SomeAction) => action.payload),
mergeMap((payload) => {
const firstActionSuccess$ = this.actions$.pipe(
ofType(actions.FIRST_ACTION_SUCCESS),
takeUntil(this.actions$.pipe(ofType(actions.FIRST_ACTION_FAIL))),
first(),
);
const secondActionsSuccess$ = this.actions$.pipe(
ofType(actions.SECOND_ACTION_SUCCESS),
takeUntil(this.actions$.pipe(ofType(actions.SECOND_ACTION_FAIL))),
first(),
);
const result$ = forkJoin(firstActionSuccess$, secondActionsSuccess$).pipe(
first(),
)
.subscribe(() => {
// perform desired tasks here
});
return [
new actions.FirstAction(),
new actions.SecondAction(),
];
}),
);
}
Pros: achievable objective
Cons: complex streams may become unwieldy and prone to errors; observables remain active until completion or failure, allowing interference from external actions
3) Implement an aggregator pattern.
Refer to Victor Savkin's presentation on State Management Patterns and Best Practices with NgRx: https://www.youtube.com/watch?v=vX2vG0o-rpM
4) Steer clear of effects for API calls. Instead, use traditional services that mimic effects but return Observables.
In your service:
public dispatchFirstAction(): Observable<void> {
this.store.dispatch(new actions.FirstAction(filter));
return this.service.someCoolMethod().pipe(
map((data) => this.store.dispatch(new actions.FirstActionSuccess(data))),
catchError((error) => {
this.store.dispatch(new actions.FirstActionFail());
return Observable.throw(error);
}),
);
}
You can later combine these in various ways, such as:
const result1$ = this.service.dispatchFirstAction();
const result2$ = this.service.dispatchSecondAction();
forkJoin(result1$, result2$).subscribe();
5) Consider trying ngxs: https://github.com/ngxs/store
Pros: reduced boilerplate, integrates well with Angular ecosystem, rapid growth
Cons: fewer features compared to NgRx