Reputation: 913
I'm working in a project with Angular using NgRx to manipulate the state with actions and reducers. A common code I see around here uses to call a subscribe inside a subscribe (which I know is generally wrong, but I don't understand why since it works). This question perfectly explains how to deal with this problem, but my case - I think - is slightly different since I need to grab a piece of the NgRx store first.
What I am currently doing is the following:
this.store.pipe(
take(1),
select(userProfile) // <-- take data from the NgRx store
).subscribe(currentUser => {
this.loggedUser = currentUser;
this.achievementService.getAchievement(
(achievment: Achievement) =>
achievement.id === currentUser.achId
).subscribe(
response => {
// ... more code
this.categoryService.getCategory(response.id).subscribe(
category=> {
// ... more code
}
);
}
);
As you can see I need to use the result of each subscribe in the next one but first I need to get a piece of the NgRx store. How would you refactor this portion of code so that it won't use multiple Observable.subscribes? And also, can you explain me the potential issues that might rise when calling a subscribe inside another?
Solution
The solution proposed by @ngfelixl works, but I want to take some time to specify a couple of things before accepting it. First, if you have multiple lines of code in a switchMap you have to explicitly return the observable:
switchMap(currentUser => {
this.loggedUser = currentUser;
return this.achievementService.getAchievement(
(achievment: Achievement) =>
achievement.id === currentUser.achId
);
})
Second, you still need a final subscription (which is the only subscription you will use). Here is the complete code sample:
this.store.pipe(select(userProfile)).pipe(
switchMap(currentUser => {
this.loggedUser = currentUser;
return this.achievementService.getAchievement(...);
}),
switchMap(achievement => this.categoryService.getCategory(achievement.id))
).subscribe(category => ...)
Upvotes: 0
Views: 819
Reputation: 6488
You can use nested mapping (switchMap
, concatMap
, mergeMap
) for handling nested observables. What you want is a list of actions based on the prior action. Imagine the first entry of the chain. This is the current user profile in your ngrx store. If this changes, everything else should change as well. Next it gets the achievements. These achievements are needed for the categories. At this point we have an observable of achievements of the current user.
We can add another switchMap
to modify the achievements observable based on another http request or whatever. We now have an observable of categories based on the user and its achievements. This categories we can for example modify with the map
operator.
this.store.pipe(select(userProfile)).pipe(
switchMap(currentUser => this.achievementService.getAchievement(...)),
switchMap(achievement => this.categoryService.getCategory(achievement.id)),
map(category => ...)
);
The other approach works, but you end up with tons of subscriptions (every time the user changes or the achievements changes a new subscription is established) and you need to unsubscribe to them as well or make sure they complete. Also this approach has a much clearer syntax so that you can read it directly. Also you have a single subscription, everything else is handled by RxJS internally.
Upvotes: 3