【问题标题】:RxJS share operator a problem inside *ngIfRxJS 共享操作符在 *ngIf 内部的一个问题
【发布时间】:2019-12-01 05:09:55
【问题描述】:

我开始精通 RxJS 运算符,但我仍然对其中一些有困难。在实现搜索组件时,我有以下代码:

      searchResult$: Observable<LunrDoc []>;
      searchResultLength$: Observable<number>;

      ngAfterViewInit(): void {

        // observable to produce an array of search hits

        this.searchResult$ = fromEvent<Event>(this.searchInput.nativeElement, 'keyup').pipe(
          debounceTime(1000),
          switchMap(e => this.ss.search((e.target as HTMLTextAreaElement).value)),
          share()
        );

        // observable to return the length of the array

        this.searchResultLength$ = this.searchResult$.pipe(
          map(sr => sr ? sr.length : 0),
          share()
        );
      }

这在模板中是这样使用的:

    <p *ngIf="(searchResultLength$ | async) > 0">
      Total Documents: {{ (searchResultLength$ | async) | number }}
    </p>

    <p *ngFor="let doc of (searchResult$ | async)">
      <span *ngIf="doc.path" [routerLink]="doc.path" style="color: darkblue; font-weight: bold; text-underline: darkblue; cursor: pointer">
        {{ doc.title }}
      </span>
      {{ doc.content.substring(0, 400) }}
    </p>

searchResult$ observable 发出一个非空数组时会发生什么,第一个段落元素中的呈现结果为“Total Documents:”,后面没有数字。 *ngFor 修饰的段落完全按预期工作。

我相信的原因是第二个async 管道在最后一个值发出后被激活并订阅了共享的 observable。所以它永远不会得到“下一个”调用。

是否可以使用 RxJS 运算符代替 share 来解决这种情况?还是我错过了什么?

【问题讨论】:

  • 您的解释听起来很有道理,您可以尝试改用shareReplay(1)
  • 我完全看不出share 的理由,你真的需要吗?
  • 共享对可观察对象进行多播,这样就不会针对每个订阅单独命中搜索 API。不过,可观察到的长度上的份额不是必需的。该代码还可以使用 ngIf 的“as”特性来避免一些重复。

标签: angular rxjs


【解决方案1】:

正如@Ingo Bürk 建议的那样。 shareReplay(1) 可能是您正在寻找的。 1 指的是缓冲区大小(它应该向您播放多少值)。

Share replay docs

我还建议避免创建多个可观察对象。

例如这会创建两个不同的订阅者。

<p *ngIf="(searchResultLength$ | async) > 0">
  Total Documents: {{ (searchResultLength$ | async) | number }}
</p>

替代方案是:

<p *ngIf="(searchResultLength$ | async) as searchResultLength > 0">
  Total Documents: {{ searchResultLength | number }}
</p>

建议的解决方案

查看代码并尝试了解功能,我认为您可以进行这些更改以获得所需的输出。

组件:

searchResult$: Observable<LunrDoc []>;
    
ngAfterViewInit(): void {
    this.searchResult$ = fromEvent<Event>(this.searchInput.nativeElement, 'keyup')
    .pipe(
        debounceTime(1000),
        switchMap(e => this.ss.search((e.target as HTMLTextAreaElement).value)),
    );
}

您不需要shareshareReplay,因为它们似乎没有提供您的模板需要的任何功能。除非模板中有更多代码在您的问题中不可见。

this.searchResultLength$ observable 似乎也是多余的,因为它只是 this.searchResult$ 返回的值上的 length 属性。

模板:

<ng-container *ngIf="{searchResult: searchResult$ | async} as vm">
    <p *ngIf="vm.searchResult.length > 0">
        Total Documents: {{ vm.searchResult }}
    </p>

    <p *ngFor="let doc of vm.searchResult">
        <span *ngIf="doc.path" [routerLink]="doc.path" style="color: darkblue; font-weight: bold; text-underline: darkblue; cursor: pointer">
            {{ doc.title }}
        </span>
        {{ doc.content.substring(0, 400) }}
    </p>
</ng-container>

包装器ng-container 将始终显示,因为ngIf 条件将评估为真值。

注意

在处理触发可观察对象的输入时,考虑查看 ditinctUntilChanged 运算符:https://www.learnrxjs.io/operators/filtering/distinctuntilchanged.html

【讨论】:

  • 这真的很有趣。 rxjs.dev/api/operators/shareReplay 文档只是一个存根,但仅凭名称就不会为后来的订阅者重播所有值吗?此外,我完全不知道我想尝试的“as”语法。
  • 我已将链接更新为一个并提供了一些解释:learnrxjs.io/operators/multicasting/sharereplay.html。是的,它会重播延迟订阅者的值(或者换句话说,在值已经发出后订阅的观察者)
  • publishBehavior 的使用怎么样?这是一个替代方案,如果是,有什么优点和缺点?也许您可以更新您的答案,而不是在评论中回答。
  • 使用修改后的解决方案进行了更新。随时使用模板中的完整代码更新您的问题
  • 我真的很喜欢这个答案,我认为这是我将使用的良好代码模式的基础。如果我可以在这篇文章中勾选 4 次,我会的。让我们保留我原来的帖子,这样任何遇到它的人都可以看到不同之处。
猜你喜欢
  • 2018-09-08
  • 1970-01-01
  • 2019-01-31
  • 2019-09-07
  • 1970-01-01
  • 2014-10-01
  • 2018-04-29
  • 2018-02-01
  • 1970-01-01
相关资源
最近更新 更多