Synchronous RxJs unsubscription not working - rxjs

Demo: https://stackblitz.com/edit/rxjs-unsubscribe-issue?file=index.ts
Below code is not working
Error: Cannot read property 'unsubscribe' of undefined
const a = (): Observable<any> =>
new Observable(sub => {
sub.next(1);
return () => {
console.log('unsubscribe');
};
});
const observer = a().subscribe(
value => {
console.log('Subscription');
observer.unsubscribe();
},
e => console.log(e),
() => console.log('complete')
);
But the following code is working
const b = (): Observable<any> =>
new Observable(sub => {
setTimeout(()=>sub.next(1),0);
return () => {
console.log('unsubscribe');
};
});
const observer2 = b().subscribe(
value => {
console.log('Subscription b');
observer2.unsubscribe();
},
e => console.log(e),
() => console.log('complete')
);
Help me understand the reason behind it

as you mentioned in the title of your question, the first example is synchronous, so you get the first value while still inside of the .subscribe() method. Naturally, observer, which is supposed to have a Subscription object hasn't been initialized yet.
If you want to unsubscribe after receiving a single value I would suggest to use .take(1)

Related

How to dispatch multiple actions from an effect in ngrx conditionally

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 do you turn an epic into an async function?

The following code works without errors:
export const myEpic = (action$: any) => action$.pipe(
ofType("TEST"),
mergeMap(() => concat(
// fires an actionCreator and triggers another epic
of(actionOne()),
// fires an actionCreator
of(actionTwo())
))
);
The problem is that I need the data from actionOne to be available before actionTwo gets fired, and it doesn't seem to be happening. So I want to make this an async function like:
export const myEpic = (action$: any) => action$.pipe(
ofType("TEST"),
mergeMap(async () => concat(
of(await actionOne()),
of(actionTwo())
))
);
This throws an error:
Uncaught Error: Actions must be plain objects. Use custom middleware for async actions.
EDIT
Other relevant code:
// main component that loads
constructor(props) {
props.dispatch(init());
}
componentDidUpdate(prevProps) {
if (prevProps.actionTwoFlag !== this.props.actionTwoFlag) {
// do stuff with result from actionOne
// error is thrown here because there's no data
}
}
// actions
export const init = () => ({ type: "TEST" });
export const actionOne = () => ({ type: "ACTION_ONE" });
export const actionOneDone = (result) => ({ type: "ACTION_ONE_DONE", payload: result });
export const actionTwo = () => ({ type: "ACTION_TWO", payload: true });
// epics
export const actionOneEpic = (action$: any) => action$.pipe(
ofType("ACTION_ONE"),
mergeMap(() =>
ajax(..).pipe(
mergeMap(result => concat(
of(actionOneDone(result)),
...
))
)
)
);
);
There are various ways to solve this.
1- One way is just using defer() operator on the actionTwo. What defer() operator would do, is execute your code on subscription, since they are concatenated, the subscription to of(actionTwo()) would be done after of(actionOne()) is completed:
export const myEpic = (action$: any) => action$.pipe(
ofType("TEST"),
mergeMap(() => concat(
of(actionOne()),
defer(() => of(actionTwo()))
))
);
2- Another option is just do a switchMap(), this would ensure too that when you create the of(actionTwo()) observable, the of(actionOne()) observable has already been emitted and finished. switchMap() also ensures sequential order, so you can safely remove the concat() operator:
export const myEpic = (action$: any) => action$.pipe(
ofType("TEST"),
mergeMap(() =>
of(actionOne()).pipe(switchMap(() => of(actionTwo())))
)
);
EDIT:
Now I think I got it, although, I am not pretty familiar with redux observable epics. I have seen a solution here: Composing and sequencing multiple epics in redux-observable
that may solve your issue two. Based on that, I will give 2 proposals.
1st proposal:
This proposal just builds an epic that push action one at first, and waits for action one done in order to push the action two.
export const myEpic = (action$: any) => action$.pipe(
ofType('TEST'),
map(() => actionOne()),
mergeMap(() => {
return action$.pipe(
ofType('ACTION_ONE_DONE'),
take(1),
map(() => actionTwo()),
);
})
);
2nd proposal:
Do it all in one epic. Since both action one and action two are related (one depend on each other) it could make sense to merge both into only one epic, it would be something like this:
export const myEpic = (action$: any) => action$.pipe(
ofType('TEST'),
map(() => actionOne()),
mergeMap(() => {
return ajax(..).pipe(
mergeMap((data) => {
return concat(
actionOneDone(action),
of(actionTwo()).mergeMap(() => /* Do action two */ actionTwoDone())
)
}),
)
})
);
Hope this helps!

RxJS to return a new object with an array payload

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!

finally() is not called when returning empty() in catch()

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>

Rxjs is getting AjaxObservable instead of ajax response

I have the next Observable i trying to filter to get the user in network but .mapTo(phone => verifyPhoneInNetwork(phone, country)) return AjaxObservable instead of the ajax response
function verifyInNetwork(contacts: any, country: string) {
const inNetworkOb = Observable
.from(contacts)
.map(contact => contact.phones)
.map(phone => verifyPhoneInNetwork(phone, country))
.first(({response}) => {
return !response.invalid && !response.exists;
})
.isEmpty()
.filter(empty => empty);
If verifyPhoneInNetowrk returns an Observable you should use switchMap like so:
function verifyInNetwork(contacts: any, country: string) {
const inNetworkOb = Observable
.from(contacts)
.map(contact => contact.phones)
.switchMap(phone => verifyPhoneInNetwork(phone, country))
.first(({response}) => {
return !response.invalid && !response.exists;
})
.isEmpty()
.filter(empty => empty);
Learn more about switchMap.

Resources