I've written the Effect below to make requests for each item in the array, and when one fails, the error isn't handled and the observable stream completes resulting actions not triggering effects any further.
Effect:
#Effect() myEffect$ = this.actions$.pipe(
ofType<myAction>(myActionTypes.myAction),
mergeMapTo(this.store.select(getAnArray)),
exhaustMap((request: any[]) => {
return zip(...request.map(item => {
return this.myService.myFunction(item).pipe(
map(response => {
return this.store.dispatch(new MyActionSuccess(response))
}),
catchError(error => {
return Observable.of(new MyActionFailure(error));
})
)
}))
})
How do I handle the error in this case?
Related
I am a back-end developer starting with front-end development for a project I am working on. The front-end uses Angular7 and NgRx. I have studied a lot in the last 4 days, but here is something I am stuck with and would appreciate your help.
I learnt that we can dispatch multiple actions from an effect in NgRx by returning an Observable array having multiple actions. I want to dispatch one of the action in the array based on a condition.
My code looks something like this
#Effect()
something$: Observable<Action> = this.actions$.pipe(
ofType(ActionType),
switchMap.(action: any) => {
return service.call(action.payload)
.pipe(
switchMap((data: ReturnType) => [
new Action1(),
new Action2(),
]),
catchError(error handling)
);
}),
);
and I want to achieve something like this
#Effect()
something$: Observable<Action> = this.actions$.pipe(
ofType(ActionType),
switchMap.(action: any) => {
return service.call(action.payload)
.pipe(
switchMap((data: ReturnType) => [
if(condition)
new Action1()
else
new Action1.1() ,
new Action2(),
]),
catchError(error handling)
);
}),
);
I think its my lack of knowledge of RxJs, which is preventing me to implement the condition.
You can dispatch multiple actions or specific actions by letting conditional ifs determine what iterable to return
I recommend you read: https://www.learnrxjs.io/operators/transformation/switchmap.html
#Effect()
something$: Observable<Action> = this.actions$.pipe(
ofType(ActionType),
switchMap(action: any) => {
return service.call(action.payload)
.pipe(
switchMap((data: ReturnType) => {
let actionsToDispatch = [];
if(condition) {
actionsToDispatch.push(new SomeAction())
} else {
actionsToDispatch.push(new SomeOtherAction())
}
return actionsToDispatch
}),
catchError(error handling)
);
}),
);
To dispatch multiple actions you can pass the action array as shown below:
#Effect()
getTodos$ = this.actions$.ofType(todoActions.LOAD_TODOS).pipe(
switchMap(() => {
return this.todoService
.getTodos()
.pipe(
switchMap(todos => [
new todoActions.LoadTodosSuccess(todos),
new todoActions.ShowAnimation()
]),
catchError(error => of(new todoActions.LoadTodosFail(error)))
);
})
);
To dispatch actions conditionally you can wrap the actions in if/else as shown below:
#Effect()
getTodos$ = this.actions$.ofType(todoActions.LOAD_TODOS).pipe(
switchMap(() => {
return this.todoService
.getTodos()
.pipe(
switchMap(todos => {
if(true) {
return new todoActions.LoadTodosSuccess(todos),
} else {
return new todoActions.ShowAnimation()
}),
catchError(error => of(new todoActions.LoadTodosFail(error)))
);
})
);
Hope that helps!
How can I make sure the tap operator is called even if a subscription is unsubscribed? Imagine the following:
function doUpdate() {
return this.http.post('somewhere', {})
.pipe(
tap(res => console.log(res))
)
}
const sub = doUpdate().subscribe(res => /* do something with res */);
setTimeout(() => sub.unsubscribe(), 1000)
In this case, I just want to prevent the subscribe action from being executed, yet I want to make sure the console log is fired, even if the request took longer than 1000 milliseconds to execute (and in this particular case, I don't even want the POST to be cancelled either).
use finalize() operator, although that will also get called when observable is completed
function doUpdate() {
return this.http.post('somewhere', {})
.pipe(
finalize(() => console.log(res))
)
}
some code to demonstrate the idea:
https://stackblitz.com/edit/rxjs-playground-test-m9ujv9
In Rxjs 7.4 tap now has three more subscribe handlers, so you can use it to get notified on subscribe, unsubscribe and finalize:
https://github.com/ReactiveX/rxjs/commit/eb26cbc4488c9953cdde565b598b1dbdeeeee9ea#diff-93cd3ac7329d72ed4ded62c6cbae17b6bdceb643fa7c1faa6f389729773364cc
So you can do:
const subscription = subject
.pipe(
tap({
subscribe: () => results.push('subscribe'),
next: (value) => results.push(`next ${value}`),
error: (err) => results.push(`error: ${err.message}`),
complete: () => results.push('complete'),
unsubscribe: () => results.push('unsubscribe'),
finalize: () => results.push('finalize'),
})
)
.subscribe();
Unsubscribing from a http request will definitely cancel the request, you could try shareReplay
function doUpdate() {
return this.http.post('somewhere', {})
.pipe(
tap(res => console.log(res)),
shareReplay()
)
}
and if that doesn't work the passing the result into a subject
function doUpdate() {
const subject = new Subject();
this.http.post('somewhere', {})
.pipe(
tap(res => console.log(res))
).subscribe(subject);
return subject;
}
I have a redux-observable epic which polls an API and I'm trying to execute three dependent, sequential http requests and gather all responses in an array.
toArray() is never executed in this case because concatMap() is not finished. I tried to move the calls inside of a mergeMap() and gather the array there, but only the last call was in the array.
timer(0, POLLING_INTERVAL).pipe(
concatMap(() => from(fetchApi(url1))),
concatMap(response => {
const url2 = 'URL based on first response';
return from(fetchApi(url2));
}),
concatMap(response => {
const url3 = 'URL based on second response';
return from(fetchApi(url3));
}),
toArray(), // expected [{response1}, {response2}, {response3}]
map(data => ({
type: ActionTypes.FETCH_SUCCESS,
payload: { data },
})),
catchError(error =>
of({
type: ActionTypes.FETCH_FAILED,
payload: { error },
}),
),
takeUntil(
action$.pipe(
ofType(ActionTypes.CANCEL_POLLING),
),
),
);
This depends on what you want to do. toArray() won't help you because timer never completes and toArray() emits only when its source completes.
Maybe you're looking for something like this:
timer(0, POLLING_INTERVAL).pipe(
concatMap(() => from(fetchApi(url1)).pipe(
concatMap(response1 => {
const url2 = 'URL based on first response';
return forkJoin([of(response1), fetchApi(url2)]);
}),
concatMap(([response1, response2]) => {
const url3 = 'URL based on second response';
return forkJoin([of(response1), of(response2), fetchApi(url3)]);
}),
)),
map(data => ({
type: ActionTypes.FETCH_SUCCESS,
payload: { data },
})),
...
I am trying to get my NgRX effect to return an action object. The action object indicates a "success", and needs to return all retrieved institutions as part of the payload. How do I do this? Please note that the block inside of the mergeMap does not work. I need to return all the retrieved institutions as part of a payload after retrieving them.
#Effect()
getInstitutions$ = this.actions$
.ofType(institutionActions.InstitutionActionTypes.GetInstitutions)
.pipe(
switchMap(() => {
const institutions = this.getInstitutions();
console.log(institutions);
return this.getInstitutions();
}),
mergeMap((institutions: Institution[]) => {
// return institutions;
return {
type: institutionActions.InstitutionActionTypes.GetInstitutionsSuccess,
payload: institutions
};
})
);
You have to operate on this.getInstitutions() as in:
#Effect()
getInstitutions$ = this.actions$
.ofType(institutionActions.InstitutionActionTypes.GetInstitutions)
.pipe(
switchMap(() => {
return this.getInstitutions().pipe(
map(institutions => ({
type: institutionActions.InstitutionActionTypes.GetInstitutionsSuccess,
payload: institutions
})),
catchError(_ => ...) //don't forget to handle errors
)
})
);
As a side note, I would encourage you to use action creators, instead of creating action objects "all over the place" - Let’s have a chat about Actions and Action Creators within NgRx
How about using "map" instaed mergeMap. Can you try following?
#Effect()
getInstitutions$ = this.actions$
.ofType(institutionActions.InstitutionActionTypes.GetInstitutions)
.pipe(
switchMap(() => {
const institutions = this.getInstitutions();
console.log(institutions);
return this.getInstitutions();
}),
map((institutions: Institution[]) => {
// return institutions;
return {
type: institutionActions.InstitutionActionTypes.GetInstitutionsSuccess,
payload: institutions
};
})
);
Hope that helps!
I wrote a generic request method that is supposed to show a loading indicator while the request is running. If an error occurs (like 404), I display the message in .catch and then return Observable.empty(), so that the following code doesn't crash (since no data is returned).
The big problem is that then .finally won't be called either. Is that a bug? Is there a workaround? Here's my code:
res = Observable
.of(true)
.do(() => this.store.dispatch(new ShowLoadingIndicatorAction()))
.switchMap(() => this.http.get(url, { headers: this.headers }))
.publishReplay(1)
.refCount()
.catch(error => {
this.messages.handleRequestError(error);
return Observable.empty();
})
.finally(() => this.store.dispatch(new HideLoadingIndicatorAction()));
// then later:
res.subscribe(doStuffWithData);
What RxJS-version are you using? It does works fine in this example:
const res$ = Rx.Observable
.of(true)
.switchMap(() => Rx.Observable.throw("Rest Error"))
.publishReplay(1)
.refCount()
.catch(error => Rx.Observable.empty())
.finally(() => console.log("Calling Finally!"));
res$.subscribe(console.info);
<script src="https://unpkg.com/rxjs/bundles/Rx.min.js"></script>