I have an Angular app with NgRx, inside one of my effects I have a non expected behavior that, turns out, is expected, but I don't know how to fix it.
My minified code is
@Effect()
detelete$ = this.actions$
.pipe(
ofType<fromActions.DeleteRequested>(fromActions.ActionTypes.DeleteRequested),
map(action => action.payload.id),
mergeMap(id =>
combineLatest([
of(id),
this.rservice.deleteById(id)
.pipe(
catchError((err, caught$) => {
this.store.dispatch(new fromActions.DeleteCancelled({id: id}));
return caught$;
})
)
])
),
map(([id, ]: [string, any]) => {
return new fromActions.DeleteSuccess({id: id});
}),
);
The reason I don't have my catchError
on the same level as my mergeMap
is that I need my id
as payload for my fromActions.DeleteCancelled
action. Plus, my service only returns a boolean, so I use combineLatest
to persist it to my onSuccess
map
.
What I'm experiencing is this catchError
is executing multiple times. Thus, dispatching my error action multiple times.
I foun that
If you return this source, the observable will effectively restart all over again and retry
In this case the source
is my caught$
.
If, inside my cacthError
I return
return of(new fromActions.DeleteCancelled({id: id}));
It will still go to my onSuccess map
. I could maybe check if my second param inside that map
is os type Action
or boolean
, but I think there's a proper way to handle it and I don't know it.
StackBlitz (uncomment subscribe
to see infinite loop)
Thanks.
You could consider this approach. Your code could looks like below:
import { EMPTY, of, Observable } from 'rxjs';
...
@Effect()
delete$ = this.actions$.pipe(
ofType<fromActions.DeleteRequested>(fromActions.ActionTypes.DeleteRequested),
map(action => action.payload.id),
switchMap((id) => this.rservice.deleteById(id).pipe(
switchMap(result => {
return result ? of(new fromActions.DeleteSuccess({id: id})) : EMPTY
}),
catchError(error => {
return of(new fromActions.DeleteCancelled({id: id}))
})
)
)
if your service is returning true
, so DeleteSuccess
action is dispatched, else an empty observable that completes immediately.
Because EMPTY
returns an observable, you have to use switchMap
, and also of
to return an observable of action DeleteSuccess
.
In case of error, DeleteCancelled
is dispatched.
Another approach could be to use @Effect({ dispatch: false })
, and manually dispatch all needed actions.
Hope it will help.