首页 > 解决方案 > Using subscribe in the correct way with NgRx

问题描述

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 => ...)

标签: angulartypescriptngrx

解决方案


您可以使用嵌套映射 ( switchMap, concatMap, mergeMap) 来处理嵌套的 observables。您想要的是基于先前操作的操作列表。想象一下链的第一个条目。这是您的 ngrx 商店中的当前用户配置文件。如果这种情况发生变化,其他一切也应该发生变化。接下来就是取得成就了。类别需要这些成就。此时,我们可以观察到当前用户的成就。

我们可以添加另一个switchMap来修改基于另一个 http 请求或其他什么的可观察的成就。我们现在有一个基于用户及其成就的可观察类别。例如,我们可以使用map运算符修改此类别。

this.store.pipe(select(userProfile)).pipe(
  switchMap(currentUser => this.achievementService.getAchievement(...)),
  switchMap(achievement => this.categoryService.getCategory(achievement.id)),
  map(category => ...)
);

另一种方法可行,但您最终会获得大量订阅(每次用户更改或成就更改时都会建立新订阅),并且您还需要取消订阅或确保它们完成。此外,这种方法具有更清晰的语法,因此您可以直接阅读它。此外,您只有一个订阅,其他一切都由 RxJS 内部处理。


推荐阅读