Angular - Observable 与在模板中多次使用的异步管道......好的做法还是坏的?

Mar*_*ark 25 components asynchronous pipe observable angular

如果我需要在组件模板中绑定来自同一个 observable 的多个属性...

例如:

<my-random-component[id]="(myObservable$ | async).id">
...
<my-random-component2[name]="(myObservable$ | async).name">
Run Code Online (Sandbox Code Playgroud)

...我最好像上面那样做(我看到很多),还是在我的 .ts 文件中订阅我的 observable 更有效,设置一个对象变量,然后绑定到它?后一种方法的想法是 observable 只会被调用一次。

问题:

  1. 上面代码中的 observable 是否在每次使用时通过 | 被调用?异步?
  2. 即使在我的模板中使用了 10 次,编译器是否在幕后执行任何效率魔法来只调用一次可观察对象?
  3. 哪种方法更好/更受欢迎?

谢谢!

aus*_*per 24

使用异步管道可以更轻松地处理订阅。与组件中的订阅不同,它会自动处理取消订阅。

也就是说,有一个比示例中显示的模式更好的模式。您可以用两种不同的方式编写组件,而不是对组件进行多次异步调用。我假设这些组件在同一个模板文件中:

    <div *ngIf="(myObservable$ | async) as myObservable">
      <my-random-component [id]="myObservable.id">
      <my-random-component2 [name]="myObservable.name">
    </div>
Run Code Online (Sandbox Code Playgroud)

包装代码有ngIf两件事:

  • 它减少了重复的代码
  • 组件在myObservable$准备好之前不存在

如果您想每次都坚持调用 async,还有一个想法:

    // COMPONENT
    name$: Observable<string>;
    id$: Observable<string>;
    
    ngOnInit() {
        // Return the exact value you want rather than the full object
    
        this.name$ = OBSERVABLE_SOURCE
        .pipe(
            map(res => res.name)
        );
    
        this.id$ = OBSERVABLE_SOURCE
        .pipe(
            map(res => res.id)
        );
    }
Run Code Online (Sandbox Code Playgroud)
    // TEMPLATE
    <my-random-component [id]="(id$ | async)">
    <my-random-component2 [name]="(name$ | async)">
Run Code Online (Sandbox Code Playgroud)

管道不会在没有订阅的情况下自动运行。你可以用它映射、点击或做任何你想做的事情,它不会运行,直到你添加async/.subscribe().


小智 15

您可以share()使用相同的 observable 并从 html 中多次调用它。像这样:

this.myObservable$ = this.anotherObservable$.pipe(share());
Run Code Online (Sandbox Code Playgroud)

那么无论你从 HTML 中调用 observable 多少次,它都只会被调用一次。

  • 我设法使用“sharedReplay()”让它工作。别问我为什么 (3认同)

Fri*_*rik 13

如果您有多个 observables,您可以将整个页面包装在一个 div 中,该 div 将所有 observables 收集到一个数据对象中,然后根据需要使用它们:

<div *ngIf="{
  observable1: myObservable1$ | async,
  observable2: myObservable2$ | async
} as data">
  ... page content
  {{data.observable1.id}}: {{data.observable1.name}}

  {{data.observable2.status}}

</div>
Run Code Online (Sandbox Code Playgroud)

注意:*ngIf="{ ... }"总是正确的。

归功于:https : //medium.com/@ofirrifo/extract-multiple-observables-with-the-async-pipe-in​​-angular-b119d22f8e05


Roh*_*ing 7

订阅处理可能是一项艰巨的任务,因此我将尝试向您解释整个场景。

异步类型

让我们首先了解有关 的一些信息AsyncPipe。每当您执行 an 操作时,您都会创建对Observable 的observableLike$ | async订阅,因此每次发出新值时都会重新渲染组件的模板。这是一个非常有用的机制,因为当组件被销毁时,Angular会处理取消订阅任务。这是Angular 文档提供的描述:

异步管道订阅 Observable 或 Promise 并返回其发出的最新值。[...] 当组件被销毁时,异步管道会自动取消订阅以避免潜在的内存泄漏。[...]

问题

也就是说,我们可以回答你的第一个问题。在您的示例中,并不是可观察量被多次调用,而是您的组件正在创建和维护同一可观察量的两个不同订阅。您可以通过在可观察对象的管道中放置tap带有 a 的运算符来验证这是否正确。console.log()

例如,如果您的 Observable 发出 HTTP 请求,它会执​​行与您相同的次数| async。(显然,发出这样的 HTTP 请求是一种不好的做法,但您明白了...)

在实践中,您创建两个订阅来获取发出的部分值,每个订阅一个,因此不存在“幕后仅调用可观察对象一次的效率魔法”。在完美的世界中,应该避免这种情况。

    <my-random-component [id]="(myObservable$ | async).id">
    <my-random-component2 [name]="(myObservable$ | async).name">
Run Code Online (Sandbox Code Playgroud)

可能的解决方案

此问题的一个可能的解决方法是使用*ngIf结构指令并使用它的模板上下文功能。您创建一个| async并为发出的值指定一个“别名”,仅进行一次订阅并访问该对象的所有属性。

    <div *ngIf="(myObservable$ | async) as myObservable">
      <my-random-component [id]="myObservable.id">
      <my-random-component2 [name]="myObservable.name">
    </div>
Run Code Online (Sandbox Code Playgroud)

可能的解决方案2

当然,您始终可以*ngIf使用 ang-container和 a来解决问题ng-template,但对于一些本应简单的事情来说,这是很多样板代码。这太冗长了,无法在整个系统中复制。

    <ng-template #myTemplate let-myObservable>
      <my-random-component [id]="myObservable.id">
      <my-random-component2 [name]="myObservable.name">
    </ng-template>
    
    <ng-container *ngTemplateOutlet="myTemplate; context: { $implicit: myObservable$ | async }">
    </ng-container>
Run Code Online (Sandbox Code Playgroud)

最佳解决方案

回答您的最后一个问题,我个人认为最好的解决方案是创建您自己的结构指令来处理模板中创建的这些订阅。

您可以隔离*ngIf模板上下文功能并使用它来集中订阅,非常类似于单例模式。它会是这样的:

    <div *ngSub="myObservable$ as myObservable">
      <my-random-component [id]="myObservable.id">
      <my-random-component2 [name]="myObservable.name">
    </div>
Run Code Online (Sandbox Code Playgroud)

此行为与之前的解决方案相同,只不过您的功能只做一件事。哦,请注意,您不需要使用AsyncPipe!

由于类型问题,最好声明一个 let 变量,而不是为 observable 提供别名,如下所示:

    <div *ngSub="myObservable$; let myObservable">
      <my-random-component [id]="myObservable.id">
      <my-random-component2 [name]="myObservable.name">
    </div>
Run Code Online (Sandbox Code Playgroud)

你可以在这里检查这个指令的实现(记得给我一个星号,哈哈),但基本上,它需要一个 Observable,保留它的订阅,并传递通过模板上下文发出的所有值。每当组件被销毁时,它也会取消订阅。看一下这个Angular 14 NPM 包,其中包含可以使用的指令。