【问题标题】:How to add extra fields in RxJS observable and wait for it to complete?如何在 RxJS observable 中添加额外的字段并等待它完成?
【发布时间】:2021-03-31 15:03:52
【问题描述】:

我有一个返回 items 的 observable,我只需要一个特定的项目,我想从 http 请求中添加一些额外的字段,这也是可观察的。下面的代码是我试图实现这一目标的方式,但它不起作用。我需要完成所有可观察项才能在ngOnInit 中获取完整的项目数据。我错过了什么?

ngOnInit() {
    myItemsObservable$(this.store, items, items.data)
        .map(items => items.find(
            item => {
                return item.id === id
            }
        ))
        .concatMap(item => {
            return this.apiService.get(`/items/${item.id}/extradata`).map(extra => ({
                ...item,
                extra
            }))
        })
        .subscribe(item => {
            // I expect item to have extra fields here.
            this.item = item
        })

        // this.item here should already be complete.
}

【问题讨论】:

    标签: javascript rxjs


    【解决方案1】:

    让我们看一下您的代码的简化版本:

    1  function ngOnInit() {
    2    myItemsObservable$().subscribe(item => this.item = item);
    3    console.log(this.item); // undefined
    4  }
    

    您实际上是在调用两个函数,它们会一个接一个地立即执行。

    第 2 行创建一个订阅对象,该对象启动可观察对象内的数据流。但是......在第 2 行之后执行并没有暂停。因此,在 observable 中的异步任务完成之前执行第 3 行。这就是为什么 this.item 在第 3 行仍然是 undefined。

    希望您能明白为什么您的评论不正确:

    // this.item here should already be complete.
    

    您正在将一个函数 (item => this.item = item) 传递给 subscribe() 方法,该方法在发生时处理来自可观察对象的排放。这是代码中实际发出值的位置。

    所以,如果我们将console.log() 移动到订阅中,this.item 将不再是未定义的:

    1  function ngOnInit() {
    2    myItemsObservable$().subscribe(item => {
    3      this.item = item;
    4      console.log(this.item); // not undefined :-)
    5    });
    6  }
    

    解决您问题的两个部分:

    如何在 RxJS observable 中添加额外的字段

    你已经在这样做了。您已使用 map 和 concatMap 运算符将源 observable 发出的值修改为您想要的值。

    ...然后等待它完成?

    嗯,你不会“等待”它本身。使用 RxJS,您正在定义数据如何流动的行为。您可以访问实际数据的唯一位置是订阅内部。

    但是...您可以直接在代码的其他部分直接引用 observable,而不是订阅,然后将数据从 observable 复制到另一个变量。

    让我们将您的代码分成几个不同的部分,以便更容易了解我们如何在不订阅的情况下引用不同的可观察源:

    id$ = this.route.paramMap.pipe(  // This could come from a form control input
        params => params.get('id')   // or some other observable source.
    );
    
    allItems$ = myItemsObservable$(this.store, items, items.data);
    
    getItem$(id) {
      return this.allItems$.pipe(
        map(items => items.find(i.id === id))
      );
    }
    
    getExtraData$(id) {
      return this.apiService.get(`/items/${id}/extradata`);
    }
    
    item$ = this.id$.pipe(
        switchMap(id => getItem$(id)),
        switchMap(item => this.getExtraData(item.id).pipe(
          map(extra => ({ ...item, ...extra }))
        ))
      );
    }
    

    看看item$ 的定义是如何以id$ 开头的?这意味着每当id$ 发出一个新值时,item$ 将自动调用getItem$(),然后getExtraData() 然后发出这个新项目。我们不需要订阅就可以做到这一点。

    我们可以简单地定义一个 observable 以从另一个 observable 开始,然后 .pipe() 排放并转换它们以满足我们的需求。

    我们基本上设计了一个 observable,它会在商店中的项目发生变化时发出,或者每当我们选择的 id$ 发出新值时发出。从某种意义上说,我们已经建立了item$ 来代表我们的item,并且它将始终是最新的,包括附加其“额外数据”。这是非常强大的。现在我们可以使用它了。

    注意item$ 的定义不需要在ngOnInit 中;它实际上可以直接在您的组件上运行。

    确实可以在我们的组件中订阅...但我们通常可以在模板中使用AsyncPipe:

    <div *ngIf="item$ | async as item">
      <h1>{{ item.name }}</h1>
      <ul>
        <li>{{ item.description  }}</li>
        <li>{{ item.someProperty }}</li>
      </ul>
    </div>
    

    如果你发现自己经常订阅你的组件,只是为了将数据复制到一个局部变量中,供你的模板使用;我会鼓励你停下来问问自己这真的有必要。大多数情况下,您可以定义一个 observable 来准确地发出您的视图所需的数据,而无需订阅。

    RxJS 提供了许多运算符和静态函数,可以轻松创建具有各种常见行为的可观察对象。

    【讨论】:

    • 这是一个很好的答案。我试图用 async 做类似的事情,但很困惑,因为 this.item 在 init 中可用,而我没有添加额外的请求。谢谢!
    【解决方案2】:

    您使用的是旧版本的 angular 和 rxjs 吗?目前,map 不是 observables 的方法。

    相反,我认为您会寻找这样的东西;

    function ngOnInit() {
      myItemsObservable$(this.store, items, items.data)
        .pipe(
          filter(item => item.id === id), // assuming your observable emits each item separately. Otherwise, use your map statement above.
          take(1), // do you need this? I'm guessing you wouldn't.
          concatMap(item => this.apiService.get(`,/items/${item.id}/extradata`)
            .pipe(
              map(extra => Object.assign({}, item, {extra})) // if I understand what you are wanting here, otherwise replace "{extra}" with "extra", or go back to your original notation
            )
          )
        )
        .subscribe(
          item => {
              // I expect item to have extra fields here.
              this.item = item
          }
        )
      // this.item here should already be complete.
    }
    

    【讨论】:

    • this.item after observable code 仍然未定义,所以它仍然不等待 observable 完成
    猜你喜欢
    • 2021-11-16
    • 2021-08-28
    • 1970-01-01
    • 1970-01-01
    • 2018-10-06
    • 1970-01-01
    • 1970-01-01
    • 2016-03-09
    • 1970-01-01
    相关资源
    最近更新 更多