Angular/RxJs什么时候应该取消订阅"订阅"

Ser*_*hon 653 subscription observable rxjs angular angular-component-life-cycle

我应该何时存储Subscription实例并unsubscribe()在NgOnDestroy生命周期中调用,何时可以忽略它们?

保存所有订阅会在组件代码中引入很多混乱.

HTTP客户端指南忽略这样的订阅:

getHeroes() {
  this.heroService.getHeroes()
                   .subscribe(
                     heroes => this.heroes = heroes,
                     error =>  this.errorMessage = <any>error);
}
Run Code Online (Sandbox Code Playgroud)

同时," 航线与导航指南"说:

最终,我们会在其他地方导航.路由器将从DOM中删除此组件并将其销毁.在此之前我们需要自己清理.具体来说,我们必须在Angular破坏组件之前取消订阅.如果不这样做可能会造成内存泄漏.

我们取消订阅我们ObservablengOnDestroy方法.

private sub: any;

ngOnInit() {
  this.sub = this.route.params.subscribe(params => {
     let id = +params['id']; // (+) converts string 'id' to a number
     this.service.getHero(id).then(hero => this.hero = hero);
   });
}

ngOnDestroy() {
  this.sub.unsubscribe();
}
Run Code Online (Sandbox Code Playgroud)

sea*_*ght 926

---编辑4 - 其他资源(2018/09/01)

最近在Angular Ben Lesh和Ward Bell的一集中讨论了如何/何时取消订阅组件的问题.讨论从大约1:05:30开始.

沃德提到right now there's an awful takeUntil dance that takes a lot of machinery和Shai Reznik提到Angular handles some of the subscriptions like http and routing.

作为回应,Ben提到现在正在进行讨论以允许Obse​​rvables挂钩Angular组件生命周期事件,而Ward建议生命周期事件的Observable,组件可以订阅它作为一种知道何时完成作为组件内部状态维护的Observable的方式.

也就是说,我们现在主要需要解决方案,所以这里有一些其他资源.

  1. takeUntil()来自RxJs核心团队成员Nicholas Jamieson 的模式建议以及帮助实施该模式的tslint规则.https://blog.angularindepth.com/rxjs-avoiding-takeuntil-leaks-fb5182d047ef

  2. 轻量级npm包,公开一个Observable操作符,该操作符将一个组件instance(this)作为参数并在此期间自动取消订阅ngOnDestroy. https://github.com/NetanelBasal/ngx-take-until-destroy

  3. 如果你没有进行AOT构建,那么上面的另一种变化与人体工程学稍好一些(但我们现在都应该做AOT). https://github.com/smnbbrv/ngx-rx-collector

  4. 自定义指令*ngSubscribe与异步管道类似,但在模板中创建嵌入式视图,因此您可以在整个模板中引用"展开"值. https://netbasal.com/diy-subscription-handling-directive-in-angular-c8f6e762697f

我在对Nicholas博客的评论中提到,过度使用takeUntil()可能表明您的组件正在尝试做太多,并且应该考虑将现有组件分离为FeaturePresentational组件.然后| async,您可以将Feature组件中的Observable转换Input为Presentational组件,这意味着无需在任何地方进行订阅.在这里阅读更多关于这种方法

---编辑3 - '官方'解决方案(2017/04/09)

我在NGConf与Ward Bell谈到了这个问题(我甚至向他展示了这个答案,他说这是正确的)但是他告诉我Angular的文档团队解决了这个未发表的问题(尽管他们正在努力让它获得批准) ).他还告诉我,我可以用即将推出的官方建议更新我的答案.

我们今后应该使用的解决方案是向private ngUnsubscribe = new Subject();所有在类代码中.subscribe()调用Observables的组件添加一个字段.

然后我们调用this.ngUnsubscribe.next(); this.ngUnsubscribe.complete();我们的ngOnDestroy()方法.

秘密酱(正如@metamaker已经指出的那样)是takeUntil(this.ngUnsubscribe)在每次调用之前.subscribe()调用,这将保证在组件被销毁时清理所有订阅.

例:

import { Component, OnDestroy, OnInit } from '@angular/core';
// RxJs 6.x+ import paths
import { filter, startWith, takeUntil } from 'rxjs/operators';
import { Subject } from 'rxjs';
import { BookService } from '../books.service';

@Component({
    selector: 'app-books',
    templateUrl: './books.component.html'
})
export class BooksComponent implements OnDestroy, OnInit {
    private ngUnsubscribe = new Subject();

    constructor(private booksService: BookService) { }

    ngOnInit() {
        this.booksService.getBooks()
            .pipe(
               startWith([]),
               filter(books => books.length > 0),
               takeUntil(this.ngUnsubscribe)
            )
            .subscribe(books => console.log(books));

        this.booksService.getArchivedBooks()
            .pipe(takeUntil(this.ngUnsubscribe))
            .subscribe(archivedBooks => console.log(archivedBooks));
    }

    ngOnDestroy() {
        this.ngUnsubscribe.next();
        this.ngUnsubscribe.complete();
    }
}
Run Code Online (Sandbox Code Playgroud)

注意:takeUntil运算符添加为最后一个运算符非常重要,以防止运算符链中的中间可观察对象发生泄漏.

---编辑2(2016/12/28)

来源5

Angular教程,路由章节现在陈述如下:"路由器管理它提供的可观察量并本地化订阅.订阅在组件被销毁时被清除,防止内存泄漏,因此我们不需要取消订阅路线可以观察到." - Mark Rajcok

这里讨论了关于路由器可观测量的Angular文档的Github问题,其中Ward Bell提到所有这些的澄清正在进行中.

---编辑1

来源4

来自NgEurope的视频中, Rob Wormald还表示您不需要取消订阅Router Observables.他还提到了http服务,并ActivatedRoute.params在此从2016年11月的视频.

---原始答案

TLDR:

对于这个问题,有(2)种Observables- 有限值和无限值.

http Observables产生有限(1)值,类似DOM的东西event listener Observables产生无限值.

如果你手动调用subscribe(不使用异步管道),那么unsubscribe无限 Observables.

不要担心有限的,RxJs会照顾他们.

来源1

我在这里找到了Angular Gitter的Rob Wormald的回答.

他说(我为了清晰而重组,重点是我的)

如果它是单值序列(如http请求),则不需要手动清理(假设您手动在控制器中订阅)

我应该说"如果它的序列完成 "(其中单个值序列,一个la http是一个)

如果它是一个无限序列,你应该取消订阅异步管道为你做的

他还在这个关于Observables的youtube视频中提到they clean up after themselves......在Observables 的背景下complete(像Promises一样,因为它们总是产生1个值并结束 - 我们从不担心取消订阅Promises以确保它们清理xhr事件听众,对吗?).

来源2

同样在Angular 2Rangle指南中,它会读取

在大多数情况下,我们不需要显式调用unsubscribe方法,除非我们想要提前取消或我们的Observable的寿命比我们的订阅更长.Observable操作符的默认行为是在发布.complete()或.error()消息后立即处理订阅.请记住,RxJS的设计大多数时候都是以"一见不醒"的方式使用.

这句话our Observable has a longer lifespan than our subscription什么时候适用?

它适用于在Observable完成之前(或之前不长)之前销毁的组件内创建订阅的情况.

我读到这意味着如果我们订阅一个http请求或一个发出10个值的observable并且我们的组件在该http请求返回之前被销毁或者已经发出10个值,我们仍然可以!

当请求返回或最终发出第10个值时,Observable将完成并清除所有资源.

来源3

如果我们从同一个Rangle指南中查看这个例子,我们可以看到Subscriptionto route.params确实需要一个,unsubscribe()因为我们不知道它们何时params会停止更改(发出新值).

该组件可以通过导航来销毁,在这种情况下,路由参数可能仍会改变(它们可能在技术上改变直到应用程序结束)并且订阅中分配的资源仍然会被分配,因为没有completion.

  • 单独调用`complete()`似乎不会清理订阅.但是,调用`next()`然后调用`complete()`,我相信`takeUntil()`只在产生一个值时停止,而不是在序列结束时停止. (15认同)
  • @seangwright那令人失望; 额外的样板很烦人. (5认同)
  • **编辑3**在活动背景下讨论,网址为https://medium.com/@benlesh/rxjs-dont-unsubscribe-6753ed4fda87 (3认同)
  • 到了 2020 年,它仍然有意义吗?或者在 Angular 10 中发生了一些变化? (3认同)
  • @seangwright在组件内部使用类型为`Subject`的成员进行快速测试,并使用`ngIf`切换以触发`ngOnInit`和`ngOnDestroy`,表明主题及其订阅永远不会完成或被处理掉(连接起来) `finally`-operator to the subscription).我必须在`ngOnDestroy`中调用`Subject.complete()`,这样订阅就可以自己清理了. (2认同)
  • 你的*---编辑3*非常有见地,谢谢!我只是有一个后续问题:如果使用`takeUnitl`方法,我们永远不必手动取消订阅任何observable?是这样的吗?此外,为什么我们需要在`ngOnDestroy`中调用`next()`,为什么不调用`complete()`? (2认同)
  • @uglycode使用这种方法,您永远不必取消订阅,除非您想要进一步自定义控制您的订阅.看看@ Firefly上面的评论.调用`complete()`不会触发`takeUntil()`.但它确实清理了`ngUnsubscribe`主题.所以`next()`清理所有其他的``complete()`清理自己. (2认同)
  • @spongessuck这些文档似乎确实是矛盾的,但是看看[RxJS 5文档](https://github.com/Reactive-Extensions/RxJS/blob/master/doc/api/core/operators/takeuntil.md) `从源可观察序列返回值,直到其他可观察序列或 Promise 产生值。`查看[源代码](https://github.com/Reactive-Extensions/RxJS/blob/master/src/modular/ observable/takeuntil.js#L18) 当 takeUntil 观察者调用“next”时,源完成。@firefly 上面提到,单独调用“complete()”似乎并不能解决问题。 (2认同)
  • @Alex这是正确的,如我在上面的回答中所解释的,HTTP请求是有限的.但这并不意味着一个始于有限的观测值总是会导致有限的流.RxJs运算符允许您操作流或数据,因此初始http请求可能成为无限数字流或模拟鼠标单击.这就是推荐上述模式的原因.无论您添加到http observable的新操作符,都可以保证它将被清除. (2认同)
  • @thorn好问题.可能是Angular团队希望我们默认让订阅使用框架的特性(`async pipe`)来管理自己.通过组合和操作组件中的Observable,RxJs允许您在与`async pipe`一起使用时不必调用`unsubscribe()`.你也经常可以将一个值"async pipe"变成一个哑组件的`@Input()`,然后使用该点的原始值.当Observable变得非常复杂但我不知道它是否应该是默认解决方案时,我发现`ngUnsubscribe()`解决方案很有用. (2认同)
  • Angular docs团队发布他们的"无证件"解决方案(一年后)的任何消息? (2认同)
  • 这个解决方案对于带有新版Rxjs的角度6是否仍然有效? (2认同)

met*_*ker 80

您不需要手动拥有大量订阅和取消订阅.使用RxJS.SubjecttakeUntil组合来处理像boss这样的订阅:

import { Subject } from "rxjs"
import { takeUntil } from "rxjs/operators"

@Component({
  moduleId: __moduleName,
  selector: "my-view",
  templateUrl: "../views/view-route.view.html"
})
export class ViewRouteComponent implements OnInit, OnDestroy {
  componentDestroyed$: Subject<boolean> = new Subject()

  constructor(private titleService: TitleService) {}

  ngOnInit() {
    this.titleService.emitter1$
      .pipe(takeUntil(this.componentDestroyed$))
      .subscribe((data: any) => { /* ... do something 1 */ })

    this.titleService.emitter2$
      .pipe(takeUntil(this.componentDestroyed$))
      .subscribe((data: any) => { /* ... do something 2 */ })

    //...

    this.titleService.emitterN$
      .pipe(takeUntil(this.componentDestroyed$))
      .subscribe((data: any) => { /* ... do something N */ })
  }

  ngOnDestroy() {
    this.componentDestroyed$.next(true)
    this.componentDestroyed$.complete()
  }
}
Run Code Online (Sandbox Code Playgroud)

@acumartini在评论中提出替代方法使用takeWhile而不是takeUntil.您可能更喜欢它,但请注意,这样您的Observable执行将不会在组件的ngDestroy上被取消(例如,当您进行耗时的计算或等待来自服务器的数据时).基于takeUntil的方法没有这个缺点,导致立即取消请求.感谢@AlexChe在评论中的详细解释.

所以这是代码:

@Component({
  moduleId: __moduleName,
  selector: "my-view",
  templateUrl: "../views/view-route.view.html"
})
export class ViewRouteComponent implements OnInit, OnDestroy {
  alive: boolean = true

  constructor(private titleService: TitleService) {}

  ngOnInit() {
    this.titleService.emitter1$
      .pipe(takeWhile(() => this.alive))
      .subscribe((data: any) => { /* ... do something 1 */ })

    this.titleService.emitter2$
      .pipe(takeWhile(() => this.alive))
      .subscribe((data: any) => { /* ... do something 2 */ })

    // ...

    this.titleService.emitterN$
      .pipe(takeWhile(() => this.alive))
      .subscribe((data: any) => { /* ... do something N */ })
  }

  // Probably, this.alive = false MAY not be required here, because
  // if this.alive === undefined, takeWhile will stop. I
  // will check it as soon, as I have time.
  ngOnDestroy() {
    this.alive = false
  }
}
Run Code Online (Sandbox Code Playgroud)

  • 我认为使用`takeUntil`和`takeWhile`之间存在显着差异.前者在触发时立即取消订阅源observable,而后者只有在源observable产生下一个值时才取消订阅.如果source observable生成一个值是一个资源消耗操作,那么在两者之间进行选择可能会超出样式首选项.见[the plunk](https://plnkr.co/edit/oaR9ULYcKC0cOrKKKLFh) (4认同)
  • 如果他只是使用 bool 来保持状态,如何使“takeUntil”按预期工作? (2认同)
  • @AlexChe 感谢提供有趣的 plunk!这对于`takeUntil` 和`takeWhile` 的一般用法来说是非常有效的,但是,不适用于我们的特定情况。当我们需要在组件销毁时取消订阅侦听器**时,我们只是在 `takeWhile` 中检查布尔值,例如 `() =&gt; alive`,因此不会使用任何耗时/内存消耗的操作,并且不同之处在于样式(ofc,对于这种特定情况)。 (2认同)
  • @metamaker 说,在我们的组件中,我们订阅了一个 `Observable`,它在内部挖掘一些加密货币,并为每一个挖出的硬币触发一个 `next` 事件,挖掘一个这样的硬币需要一天的时间。使用`takeUntil`,一旦在我们的组件销毁期间调用`ngOnDestroy`,我们将立即取消订阅源挖掘`Observable`。因此,挖掘`Observable` 函数能够在此过程中立即取消其操作。 (2认同)
  • OTOH,如果我们使用 `takeWhile`,在 `ngOnDestory` 中我们只设置布尔变量。但是挖掘 `Observable` 函数可能仍然工作长达一天,只有在它的 `next` 调用期间,它才会意识到没有订阅活动,它需要取消。 (2认同)
  • 在这一天,挖掘功能期间会完成不必要的工作,并且您被破坏的组件也不符合垃圾收集的条件。 (2认同)
  • “这里可能不需要 this.alive = false”是错误信息。您有一个强引用,除非您明确将其设置为未定义,否则它永远不会从“true”更改为“false”或“undefined”。 (2认同)

Ste*_*ens 64

Subscription类有一个有趣的特性:

表示可处理的资源,例如Observable的执行.订阅有一个重要的方法,取消订阅,不带参数,只是处置订阅所持有的资源.
此外,订阅可以通过add()方法组合在一起,该方法将子订阅附加到当前订阅.当订阅被取消订阅时,其所有子女(及其孙子女)也将被取消订阅.

您可以创建一个聚合订阅对象,该对象将您的所有订阅分组.您可以通过创建一个空的Subscription并使用其add()方法向其添加订阅来完成此操作.当您的组件被销毁时,您只需要取消订阅聚合订阅.

@Component({ ... })
export class SmartComponent implements OnInit, OnDestroy {
  private subscriptions = new Subscription();

  constructor(private heroService: HeroService) {
  }

  ngOnInit() {
    this.subscriptions.add(this.heroService.getHeroes().subscribe(heroes => this.heroes = heroes));
    this.subscriptions.add(/* another subscription */);
    this.subscriptions.add(/* and another subscription */);
    this.subscriptions.add(/* and so on */);
  }

  ngOnDestroy() {
    this.subscriptions.unsubscribe();
  }
}
Run Code Online (Sandbox Code Playgroud)

  • 这个答案的一个小好处:您不必检查 `this.subscriptions` 是否为空 (5认同)
  • 我更喜欢这种方法,因为它更清楚我们想要做什么:取消订阅。其他方法会在管道中引入噪音。 (4认同)
  • 我正在使用这种方法。想知道这是否比使用 takeUntil() 的方法更好,就像在接受的答案中一样......缺点? (3认同)
  • 请参阅 https://medium.com/@benlesh/rxjs-dont-unsubscribe-6753ed4fda87 进一步讨论官方`takeUntil`方法与这种收集订阅和调用`unsubscribe`的方法。(这种方法对我来说似乎更清晰。) (3认同)
  • 只需避免像 `sub = subsciption.add(..).add(..)` 这样的 add 方法链接,因为在许多情况下它会产生意想不到的结果 https://github.com/ReactiveX/rxjs/issues/2769#issuecomment -345636477 (2认同)

Mou*_*eer 29

关于Angular组件内可观察的取消订阅的一些最佳实践:

引用自 Routing & Navigation

订阅组件中的observable时,您几乎总是安排在组件被销毁时取消订阅.

有一些特殊的可观测量,这是没有必要的.ActivatedRoute可观察量是例外.

ActivatedRoute及其可观察对象与路由器本身绝缘.当不再需要路由器时,路由器会销毁路由组件,并且注入的ActivatedRoute会随之消失.

无论如何,请随意取消订阅.它是无害的,从来都不是一种糟糕的做法.

并在回应以下链接:

我收集了一些关于Angular组件中可观察的取消订阅的最佳实践,以便与您分享:

  • http观察到的退订是有条件的,我们应该考虑到"认购回调"的效果组件在由个案基础上破坏后被运行.我们知道有角度取消订阅并清理http可观察物本身(1),(2).虽然从资源的角度来看这是真的,但它只讲述了一半的故事.假设我们正在讨论http从组件内部直接调用,http响应时间比需要的时间长,因此用户关闭了组件.该subscribe()处理器将组件是否关闭和销毁仍然甚至称.这可能会产生不必要的副作用,在更糟糕的情况下会使应用程序状态中断.如果回调中的代码试图调用刚被处理掉的东西,它也会导致异常.然而,偶尔他们是偶然的.比如,假设您正在创建一个电子邮件客户端,并在电子邮件发送完成后触发声音 - 即使组件已关闭,您仍然希望发生这种情况(8).
  • 无需取消订阅完成或错误的可观察对象.但是,这样做没有害处(7).
  • AsyncPipe尽可能多地使用,因为它会自动取消订阅组件销毁时的可观察性.
  • 取消订阅ActivatedRoute可观察对象,route.params如果它们是在嵌套(在组件选择器中添加内部tpl)或动态组件内订阅的,因为只要父/主机组件存在,它们可能会多次订阅.无需在上述Routing & Navigation文档中提到的其他方案中取消订阅.
  • 取消订阅通过Angular服务公开的组件之间共享的全局可观察对象,例如,只要组件初始化,它们可能会多次订阅.
  • 无需取消订阅应用程序范围服务的内部可观察量,因为此服务永远不会被销毁,除非您的整个应用程序被销毁,没有真正的理由取消订阅并且没有内存泄漏的可能性.(6).

    注意:关于作用域服务,即组件提供程序,它们在组件销毁时被销毁.在这种情况下,如果我们订阅此提供程序中的任何observable,我们应该考虑OnDestroy根据文档使用生命周期钩子取消订阅,该生命周期钩子将在服务被销毁时调用.
  • 使用抽象技术可以避免因取消订阅而导致的任何代码混乱.您可以管理与您的订阅takeUntil (3) ,或者你可以使用这个npm 在提到(4)最简单的方法,从观测量的角度退订.
  • 始终取消订阅FormGroupform.valueChanges和的观察者form.statusChanges
  • 始终取消订阅Renderer2服务的可观察性renderer2.listen
  • 取消订阅其他可观察的其他内容作为内存泄漏保护步骤,直到Angular Docs明确告诉我们哪些observable不需要取消订阅(检查问题:(5)RxJS Unsubscribing(Open)的文档).
  • 额外:总是使用Angular方法绑定事件,如HostListener角度关注如果需要删除事件侦听器,并防止由于事件绑定导致的任何潜在内存泄漏.

一个不错的最后提示:如果您不知道是否正在自动取消订阅/完成一个observable,请添加一个complete回调subscribe(...)并检查它是否在组件被销毁时被调用.

  • @Tim首先,"无论如何,请随意取消订阅.这是无害的,从来不是一种糟糕的做法.对于你的问题,这取决于.如果多次启动子组件(例如,在`ngIf`中添加或动态加载),则必须取消订阅以避免向同一观察者添加多个订阅.否则没必要.但我更喜欢在子组件内部取消订阅,因为这使得它更容易重复使用并与其使用方式隔离开来. (2认同)

Chu*_*Sun 16

这取决于.如果通过调用someObservable.subscribe(),您开始占用一些必须在组件的生命周期结束后手动释放的资源,那么您应该调用theSubscription.unsubscribe()以防止内存泄漏.

让我们仔细看看你的例子:

getHero()返回结果http.get().如果您查看angular 2 源代码,请http.get()创建两个事件侦听器:

_xhr.addEventListener('load', onLoad);
_xhr.addEventListener('error', onError);
Run Code Online (Sandbox Code Playgroud)

通过调用unsubscribe(),您可以取消请求以及听众:

_xhr.removeEventListener('load', onLoad);
_xhr.removeEventListener('error', onError);
_xhr.abort();
Run Code Online (Sandbox Code Playgroud)

请注意,这_xhr是特定于平台的,但我认为可以安全地假设它是XMLHttpRequest()您的情况.

通常,这足以证明手动unsubscribe()呼叫.但根据WHATWG规范,XMLHttpRequest()即使有附加事件监听器,它也会在"完成"后进行垃圾收集.所以我想这就是为什么angular 2官方指南省略unsubscribe()并让GC清理听众.

至于你的第二个例子,它取决于你的实现params.截至今天,角度官方指南不再显示取消订阅params.我再次查看了src,发现这params只是一个BehaviorSubject.由于没有使用事件监听器或定时器,并且没有创建全局变量,因此省略它应该是安全的unsubscribe().

你的问题的底线是始终调用unsubscribe()防止内存泄漏,除非你确定observable的执行不会创建全局变量,添加事件监听器,设置定时器,或做任何导致内存泄漏的事情.

如有疑问,请查看该可观察的实现.如果observable已经将一些清理逻辑写入其中unsubscribe(),这通常是构造函数返回的函数,那么您有充分的理由认真考虑调用unsubscribe().


小智 6

Angular 2官方文档提供了何时取消订阅以及何时可以安全忽略的说明.看看这个链接:

https://angular.io/docs/ts/latest/cookbook/component-communication.html#!#bidirectional-service

查找标题为Parent的段落,子项通过服务进行通信,然后是蓝色框:

请注意,我们捕获订阅并在AstronautComponent销毁时取消订阅.这是一个内存泄漏保护步骤.此应用程序中没有实际风险,因为AstronautComponent的生命周期与应用程序本身的生命周期相同.在更复杂的应用程序中,这并非总是如此.

我们不会将此保护添加到MissionControlComponent,因为作为父级,它控制MissionService的生命周期.

我希望这可以帮助你.

  • 作为一个组成部分你永远不知道你是否是一个孩子.因此,您应该始终取消订阅订阅作为最佳做法. (3认同)
  • MissionControlComponent 的关键不在于它是否是父级,而是组件本身提供服务。当 MissionControl 被销毁时,服务和对服务实例的任何引用也会被销毁,因此不存在泄漏的可能性。 (2认同)

Wil*_*ilt 6

对于在发出结果后直接完成的可观察量,AsyncSubject例如来自 http 请求的可观察量等,您不需要取消订阅。调用这些并没有什么坏处unsubscribe(),但如果可观察的是closedunsubscribe 方法将根本不执行任何操作

if (this.closed) {
  return;
}
Run Code Online (Sandbox Code Playgroud)

当您拥有长期存在的可观察量并随着时间的推移发出多个值(例如 aBehaviorSubject或 a ReplaySubject)时,您需要取消订阅以防止内存泄漏。

您可以轻松创建一个可观察量,该可观察量在使用管道运算符从此类长期存在的可观察量发出结果后直接完成。在这里的一些答案中take(1)提到了管道。但我更喜欢管子first()。不同之处take(1)在于它将:

EmptyError如果 Observable 在发送任何下一个通知之前完成,则向观察者的错误回调传递一个错误回调。

第一个管道的另一个优点是您可以传递一个谓词来帮助您返回满足特定条件的第一个值:

const predicate = (result: any) => { 
  // check value and return true if it is the result that satisfies your needs
  return true;
}
observable.pipe(first(predicate)).subscribe(observer);
Run Code Online (Sandbox Code Playgroud)

First 将在发出第一个值后直接完成(或者在传递函数参数时满足您的谓词的第一个值),因此无需取消订阅。

有时你不确定你是否有一个长期存在的可观察对象。我并不是说这是一个好的做法,但您可以随时添加管道first以确保不需要手动取消订阅。在可观察对象上添加一个first仅发出一个值的附加管道并没有什么坏处。

在开发过程中,如果源可观察对象发出多个事件,您可以使用将失败管道single这可以帮助您探索 observable 的类型以及是否有必要取消订阅。

observable.pipe(single()).subscribe(observer);
Run Code Online (Sandbox Code Playgroud)

和看起来非常相似,两个管道都可以采用可选谓词,但差异很重要,并且在这个 stackoverflow 答案中first很好地总结了:single

第一的

第一个项目出现后就会发出。之后将立即完成。

单身的

如果源可观察对象发出多个事件,则会失败。


请注意, 我试图在回答中尽可能准确和完整,并参考官方文档,但如果缺少重要内容,请发表评论......


Wan*_*lle 6

Angular 16引入了一个新函数来减轻对 observable 的破坏takeUntilDestroyed

data$ = http.get('...').pipe(takeUntilDestroyed()).subscribe(...);
Run Code Online (Sandbox Code Playgroud)

默认情况下,应该在构造函数内部调用它。要想在其他地方使用它,那DestroyRef就是必要的。

destroyRef = inject(DestroyRef);

ngOnInit(){
   data$ = http.get('...').subscribe(...)

   this.destroyRef.onDestroy(() => {
      data$.unsubscribe()
   })
}
Run Code Online (Sandbox Code Playgroud)


JoG*_*JoG 5

基于:使用类继承挂钩到Angular 2组件生命周期

另一种通用方法:

export abstract class UnsubscribeOnDestroy implements OnDestroy {
  protected d$: Subject<any>;

  constructor() {
    this.d$ = new Subject<void>();

    const f = this.ngOnDestroy;
    this.ngOnDestroy = () => {
      f();
      this.d$.next();
      this.d$.complete();
    };
  }

  public ngOnDestroy() {
    // no-op
  }

}
Run Code Online (Sandbox Code Playgroud)

并使用:

@Component({
    selector: 'my-comp',
    template: ``
})
export class RsvpFormSaveComponent extends UnsubscribeOnDestroy implements OnInit {

    constructor() {
        super();
    }

    ngOnInit(): void {
      Observable.of('bla')
      .takeUntil(this.d$)
      .subscribe(val => console.log(val));
    }
}
Run Code Online (Sandbox Code Playgroud)


小智 5

订阅本质上只有一个 unsubscribe() 函数来释放资源或取消 Observable 执行。 在 Angular 中,当组件被销毁时,我们必须取消订阅 Observable。幸运的是,Angular 有一个 ngOnDestroy 钩子,可以在组件被销毁之前调用,这使得开发人员能够在此处提供清理人员,以避免挂起订阅、打开门户以及将来可能出现的问题

@Component({...})
export class AppComponent implements OnInit, OnDestroy {
    subscription: Subscription 
    ngOnInit () {
        var observable = Rx.Observable.interval(1000);
        this.subscription = observable.subscribe(x => console.log(x));
    }
    ngOnDestroy() {
        this.subscription.unsubscribe()
    }
}
Run Code Online (Sandbox Code Playgroud)

我们将 ngOnDestroy 添加到 AppCompoennt 中,并在 this.subscription Observable 上调用取消订阅方法

如果有多个订阅:

@Component({...})
export class AppComponent implements OnInit, OnDestroy {
    subscription1$: Subscription
    subscription2$: Subscription 
    ngOnInit () {
        var observable1$ = Rx.Observable.interval(1000);
        var observable2$ = Rx.Observable.interval(400);
        this.subscription1$ = observable.subscribe(x => console.log("From interval 1000" x));
        this.subscription2$ = observable.subscribe(x => console.log("From interval 400" x));
    }
    ngOnDestroy() {
        this.subscription1$.unsubscribe()
        this.subscription2$.unsubscribe()
    }
}
Run Code Online (Sandbox Code Playgroud)


归档时间:

查看次数:

149908 次

最近记录:

6 年 前