Reputation: 1628
I am writing an effect that requires the results of several separate service calls (returning as observables) before completing it. The first service call must complete before 3 more (which can be async), and then I build my action list. I have it working, but it feels like it is needlessly nested.
@Effect() loadMyData$: Observable<any> = this.actions$.pipe(
ofType(fromMyData.LOAD_DATA),
switchMap((action: fromMyData.Load) => {
return this.dataService.openMyData(action.payload).pipe(
switchMap(() => this.dataService.getShapes()),
switchMap(shapeData => {
return this.dataService.getCircles().pipe(
switchMap((circleData) => {
return this.dataService.getTriangles().pipe(
switchMap((triangleData) => {
const flatMyDataWithReferences: any = {
triangleName: triangleData.name,
shapeName: shapeData.name
};
return [
new fromShapes.Load(),
new fromBalls.Load(),
new fromCircles.LoadListSuccess(
this.organizeCircles(circleData)),
new fromMyData.LoadSuccess(flatMyDataWithReferences),
new fromUi.AddMessage({
type: MessageType.Success, message: 'Successfully loaded my data ' +
shapeData.name +
' from: ' + action.payload
})
];
}),
catchError((err) => this.myDataLoadErrorEvents(err))
);
}),
catchError((err) => this.myDataLoadErrorEvents(err))
);
}),
catchError((err) => this.myDataLoadErrorEvents(err))
);
}),
catchError((err) => this.myDataLoadErrorEvents(err))
);
Basically, in my example code here, once I have called and returned from the dataService.openMyData
call, I would like to do these calls in parallel:
dataService.getShapes
dataService.getCircles
dataService.getTriangles
Once they all complete, I would like use their returned data within my array of actions to put in the return [new etc...]
to wrap up the effect.
hoping someone has a more elegant way to approach the 3 intermediary service calls than this ugly (and needless) indentation hell...
I looked around and found that some people are using forkJoin to wait for the result of multiple observables, but it appears they can't use the result of that forkJoin observable as a return on which to operate. For example, this code below tells me that I am not creating an observable in the end, which is what my effect requires..
@Effect() loadMyData$: Observable<any> = this.actions$.pipe(
ofType(fromMyData.LOAD_DATA),
switchMap((action: fromMyData.Load) => {
return this.dataService.openMyData(action.payload).pipe(
switchMap(() => {
return forkJoin(
this.dataService.getShapes(),
this.dataService.getCircles(),
this.dataService.getTriangles()
).pipe(
map(joinResult => {
const [shapeData, circleData, triangleData] = joinResult;
const flatMyDataWithReferences: any = {
triangleName: triangleData.name,
shapeName: shapeData.name
};
return [
new fromShapes.Load(),
new fromBalls.Load(),
new fromCircles.LoadListSuccess(
this.organizeCircles(circleData)),
new fromMyData.LoadSuccess(flatMyDataWithReferences),
new fromUi.AddMessage({
type: MessageType.Success, message: 'Successfully loaded my data ' +
shapeData.name +
' from: ' + action.payload
})
];
})
);
}),
catchError((err) => this.myDataLoadErrorEvents(err))
);
}),
catchError((err) => this.myDataLoadErrorEvents(err))
);
Upvotes: 2
Views: 5249
Reputation: 1628
forkJoin
to Wait for Multiple Observables to CompleteIt turns out my question needed two important solutions to make it work as a proper stream of observable actions, which is what an effect requires as a return. The first part is @llsanchez response- using the return forkJoin().pipe(map(response => response.stuff));
form to complete the effect with the forkJoin
, the pipe
and the map
being the correct sequence.
map
or mergeMap
to Return Action Observable(s) in your EffectFor things to work properly in an effect with multiple resulting actions (which my example has), you must substitute the map
operator with mergeMap
as follows:
@Effect() loadMyData$: Observable<any> = this.actions$.pipe(
ofType(fromMyData.LOAD_DATA),
switchMap((action: fromMyData.Load) => {
return this.dataService.openMyData(action.payload).pipe(
switchMap(() => {
return forkJoin(
this.dataService.getShapes(),
this.dataService.getCircles(),
this.dataService.getTriangles()
).pipe(
mergeMap(joinResult => {
const [shapeData, circleData, triangleData] = joinResult;
const flatMyDataWithReferences: any = {
triangleName: triangleData.name,
shapeName: shapeData.name
};
return [
new fromShapes.Load(),
new fromBalls.Load(),
new fromCircles.LoadListSuccess(
this.organizeCircles(circleData)),
new fromMyData.LoadSuccess(flatMyDataWithReferences),
new fromUi.AddMessage({
type: MessageType.Success, message: 'Successfully loaded my data ' +
shapeData.name +
' from: ' + action.payload
})
];
})
);
}),
catchError((err) => this.myDataLoadErrorEvents(err))
);
}),
catchError((err) => this.myDataLoadErrorEvents(err))
);
In summary, to return a proper stream of Actions in an effect:
map
, as in the form:return forkJoin().pipe(map(response => response.action))
mergeMap
, as in the form:return forkJoin().pipe(mergeMap(response => [response.action1, response.action2]))
Upvotes: 2
Reputation: 271
With forkJoin you can use the result, don't aware:
forkJoin(
this.dataService.getShapes(),
this.dataService.getCircles(),
this.dataService.getTriangles()
).pipe(
map(res => {
// Here you can use the result. That result is an array of last emitted value for each observable. The order is the same that you declare in observable array.
const [shapes, circles, triangles] = res;
})
);
One important thing to consider is that if one of request fails, you cannot receive any value and should catch it properly.
Here can see more information forkJoin
Upvotes: 3