简体   繁体   English

Angular - 可观察到异步 pipe 在模板中多次使用......好习惯还是坏习惯?

[英]Angular - Observable with async pipe used multiple times in template... Good Practice or Bad?

If I have the need to bind multiple properties from the same observable within my component template...如果我需要在我的组件模板中绑定来自同一个可观察对象的多个属性......

For example:例如:

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

...am I better off doing it like I have above (which I see a lot), or is it more efficient to subscribe to my observable inside my.ts file, set a single object variable, and then bind to that? ...我最好像上面那样做(我看到很多),还是在 my.ts 文件中订阅我的 observable,设置单个 object 变量,然后绑定到那个更有效? The idea with the latter approach being that the observable will only be called once.后一种方法的想法是 observable 只会被调用一次。

Questions:问题:

  1. Does the observable in the above code get called each time it is used via |上述代码中的 observable 是否在每次通过 | 使用时都会被调用? async?异步?
  2. Does the compiler do any efficiency magic behind the scenes to only call the observable once even if used 10 times w/in my template?即使在我的模板中使用了 10 次,编译器是否会在幕后执行任何效率魔法以仅调用一次可观察对象?
  3. Which approach is better/preferred?哪种方法更好/首选?

Thanks!谢谢!

Using the async pipe makes handling subscriptions much easier.使用异步管道可以更轻松地处理订阅。 It automatically handles unsubscribing unlike subscribing in the component.与组件中的订阅不同,它会自动处理取消订阅。

That said, there is a better pattern than what the example is showing.也就是说,有一个比示例中显示的模式更好的模式。 Rather than having multiple async calls on components, you can write it 2 different ways.您可以用两种不同的方式编写组件,而不是对组件进行多次异步调用。 I'm assuming these components are in the same template file:我假设这些组件在同一个模板文件中:

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

Wrapping the code in ngIf does 2 things:将代码包装在ngIf做两件事:

  • It cuts down on duplicate code它减少了重复的代码
  • The components do not exist until myObservable$ is ready组件在myObservable$准备好之前不存在

There's also one more idea if you want to stick with calling async every single time:如果您想每次都坚持调用 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)
        );
    }
    // TEMPLATE
    <my-random-component [id]="(id$ | async)">
    <my-random-component2 [name]="(name$ | async)">

Pipes do not automatically run without a subscription.管道不会在没有订阅的情况下自动运行。 You can map, tap, or do anything else you want with it and it will not run until you add async/.subscribe() .你可以用它映射、点击或做任何你想做的事情,它不会运行,直到你添加async/.subscribe()

If you have multiple observables, you could wrap your entire page in a div that collects all the observables into a data object and then use them as needed :如果您有多个 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>

Note: the *ngIf="{ ... }" is always true.注意: *ngIf="{ ... }"始终为真。

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

You can just use share() to use the same observable and call it multiple times from html.您可以使用share()来使用相同的 observable 并从 html 多次调用它。 Like this:像这样:

this.myObservable$ = this.anotherObservable$.pipe(share());

Then no matter how many times you call the observable from the HTML, it is called only once.那么无论你从 HTML 中调用 observable 多少次,它只会被调用一次。

Another approach would be separating both structure and content rendering responsibilities without being bound to *ngIf .另一种方法是将结构和内容渲染职责分开,而不受*ngIf的约束。

You could use ng-template and ng-container together with context:您可以将ng-templateng-container与上下文一起使用:

<ng-template #userTemplate let-user>
  <user-address [zipCode]="user?.zipCode"></user-address>
  <user-car [carCode]="user?.carCode"></user-car>
</ng-template>

<ng-container
  *ngTemplateOutlet="
    userTemplate;
    context: { $implicit: user$ | async }
  "
>
</ng-container>

Subscription handling can be a hard task, so I will try to explain the whole scenario to you.订阅处理可能是一项艰巨的任务,因此我将尝试向您解释整个场景。

AsyncPype异步类型

Let's begin with some information about the AsyncPipe .让我们从有关AsyncPipe的一些信息开始。 Whenever you do an observableLike$ | async每当你做一个observableLike$ | async observableLike$ | async , you are creating a subscription to the Observable , so your component's template will be re-render every time a new value is emitted. observableLike$ | async ,您正在创建对Observable的订阅,因此每次发出新值时都会重新渲染组件的模板。 This is a very useful mechanism because Angular handles with the unsubscribe task when the component is destroyed.这是一个非常有用的机制,因为 Angular 在组件被销毁时处理取消订阅任务。 This is the description provided by Angular Documentation :这是Angular 文档提供的描述:

The async pipe subscribes to an Observable or Promise and returns the latest value it has emitted.异步 pipe 订阅 Observable 或 Promise 并返回它发出的最新值。 [...] When the component gets destroyed, the async pipe unsubscribes automatically to avoid potential memory leaks. [...] 当组件被销毁时,异步 pipe 会自动取消订阅以避免潜在的 memory 泄漏。 [...] [...]

Problem问题

That said, we can answer your first question.也就是说,我们可以回答您的第一个问题。 In your example, it's not that the observable is being called multiple times, is your component that is creating and maintaining two different subscriptions of the same observable.在您的示例中,并不是多次调用 observable,而是您的组件正在创建和维护同一个 observable 的两个不同订阅。 You can verify that this is true by placing a tap operator with a console.log() in the observable's pipe.你可以通过在 observable 的 pipe 中放置一个带有console.log()tap操作符来验证这是真的。

If your Observable does an HTTP request, for example, it will do so as many times as you | async例如,如果您的 Observable 执行 HTTP 请求,它会执行与您一样多的请求| async | async it. | async它。 (Obsviously, it's a bad practice to make an HTTP request like this, but you get the idea...) (显然,像这样发出 HTTP 请求是一种不好的做法,但你明白了......)

In practice, you are creating two subscriptions to get parts of the value emitted, one in each, so there is no "efficiency magic behind the scenes to call the observable just once".在实践中,您正在创建两个订阅来获取发出的部分值,每个订阅一个,因此没有“幕后的效率魔法只调用一次 observable”。 In a perfect world, this should be avoided.在一个完美的世界中,应该避免这种情况。

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

Possible Solution可能的解决方案

A possible workaround for this problem is to use the *ngIf structural directive and work with the template-context functionality of it.此问题的一种可能解决方法是使用*ngIf结构指令并使用它的模板上下文功能。 You make a | async你做一个| async | async and give an "alias" for the value emitted, making just one subscription and accessing all of the attributes of the object. | async并为发出的值提供一个“别名”,只进行一次订阅并访问 object 的所有属性。

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

Possible Solution 2可能的解决方案 2

Of course, you can always solve the *ngIf problem with a ng-container and a ng-template , but that's a lot of boilerplate code for something that should be simple.当然,您总是可以使用ng-containerng-template来解决*ngIf问题,但是对于应该很简单的事情来说,这是很多样板代码。 This is too verbose to replicate across an entire system.这太冗长了,无法在整个系统中复制。

    <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>

Best Solution最佳解决方案

Answering your last question, I personally think that the best solution is to create your own structural directive to handle these subscriptions created in the template.回答你最后一个问题,我个人认为最好的解决方案是创建自己的结构指令来处理模板中创建的这些订阅。

You can isolate the *ngIf template-context functionality and use it just to center the subscription, pretty much like a singleton pattern.您可以隔离*ngIf模板上下文功能并使用它来集中订阅,非常类似于 singleton 模式。 It will be something like that:它会是这样的:

    <div *ngSub="myObservable$ as myObservable">
      <my-random-component [id]="myObservable.id">
      <my-random-component2 [name]="myObservable.name">
    </div>

This behavior is the same as the previous solution, except you have a functionality that does only one thing .此行为与之前的解决方案相同,只是您有一个只做一件事的功能。 Oh, and note that you don't need to do a | async哦,请注意,您不需要执行| async | async ! | async

Because of type issues, it's better if you declare a let variable, rather than providing an alias for the observable, like this:由于类型问题,最好声明一个 let 变量,而不是为 observable 提供别名,如下所示:

    <div *ngSub="myObservable$; let myObservable">
      <my-random-component [id]="myObservable.id">
      <my-random-component2 [name]="myObservable.name">
    </div>

You can check this Directive Implementation here (remember to give me a star lol), but basically, it takes an Observable, keeps a subscription of it, and passes all the values emitted via template context.你可以在这里查看这个指令实现(记得给我一个星号),但基本上,它需要一个 Observable,保留它的订阅,并传递通过模板上下文发出的所有值。 It also unsubscribes whenever the component is destroyed.每当组件被销毁时,它也会取消订阅。 Take a look at this Angular 14 NPM package which contains this directive ready to use.看看这个Angular 14 NPM package ,它包含了可以使用的指令。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM