当前位置: 首页 > 知识库问答 >
问题:

Angular/RXJS我何时应该取消对`subscription`的订阅

公冶威
2023-03-14

什么时候应该存储subscription实例并在NgOnDestroy生命周期中调用unsubscribe(),什么时候可以简单地忽略它们?

保存所有订阅会给组件代码带来很多混乱。

HTTP客户端指南忽略订阅,如下所示:

getHeroes() {
  this.heroService.getHeroes()
                  .subscribe(
                     heroes => this.heroes = heroes,
                     error =>  this.errorMessage = <any>error);
}

在同一时间的路线和导航指南说:

最终,我们会在别的地方导航。路由器将从DOM中删除该组件并销毁它。在那发生之前我们需要自己清理干净。具体地说,我们必须在Angular破坏组件之前退订。否则可能会造成内存泄漏。

我们在ngondestroy方法中取消订阅observable

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();
}

共有2个答案

桓兴腾
2023-03-14

您不需要有一堆订阅和取消订阅手动。使用Subject和takeUntil组合来像老板一样处理订阅:

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()
  }
}

@acumartini在评论中提出的另一种方法使用takeWhile而不是TakeUntil。您可能更喜欢它,但请注意,这样您的可观察执行不会在组件的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 */ })
  }

  ngOnDestroy() {
    this.alive = false
  }
}
于飞飙
2023-03-14

在最近的Angular Adventures中,Ben Lesh和Ward Bell讨论了如何/何时在组件中退订的问题。讨论大约在1点05时30分开始。

Ward提到,现在有一个可怕的Take Until dance需要很多机器,Shai Reznik提到Angular处理一些订阅,如http和Routing

作为回应,Ben提到,现在正在讨论允许可观察到的组件生命周期事件挂钩,而Ward建议一个组件可以订阅的生命周期事件的可观察到的事件,作为知道何时完成作为组件内部状态维护的可观察到的事件的一种方式。

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

>

  • RxJs核心团队成员Nicholas Jamieson对TakeUntil()模式的推荐,以及帮助实施它的tslint规则。https://ncjamieson.com/aviding-takeuntil-leaks/

    轻量级npm包,它公开了一个可观察的运算符,该运算符将组件实例(this)作为参数,并在ngondestroy期间自动取消订阅。https://github.com/netanelbasal/ngx-take-until-destroy

    如果您不是在做AOT构建(但我们现在都应该在做AOT),那么上面的另一个变体具有更好的人机工程学。https://github.com/smnbbrv/ngx-rx-collector

    自定义指令*ngsubscribe,它的工作方式类似于异步管道,但在模板中创建了一个嵌入式视图,因此您可以在整个模板中引用“unwrapped”值。https://netbasal.com/diy-subscription-handling-directive-in-angular-c8f6e762697f

    我在对Nicholas的博客的评论中提到,过度使用takeuntil()可能表明您的组件试图做得太多,应该考虑将现有组件分离为特性组件和表示组件。然后,您可以将Feature组件的可观察对象Async转换为表示组件的Input,这意味着在任何地方都不需要订阅。请在此处阅读有关此方法的更多信息

    我在NGConf上和Ward Bell讨论了这个问题(我甚至给他看了这个他说正确的答案),但他告诉我Angular的文档团队有一个解决这个问题的方案,但还没有公布(尽管他们正在努力让它得到批准)。他还告诉我,我可以用即将发布的正式建议来更新我的SO答复。

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

    然后我们调用this.ngunsubscribe.next();在ngondestroy()方法中,this.ngunsubscribe.complete();

    秘诀(正如@metamaker已经提到的)是在我们的每次.subscribe()调用之前调用takeuntil(this.ngunsubscribe),这将保证在组件被销毁时所有订阅都将被清理。

    示例:

    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();
        }
    }
    

    注意:添加takeuntil运算符作为最后一个运算符是很重要的,以防止运算符链中的中间可观察值泄漏。

    资料来源5

    Angular教程中的路由章节现在说明如下:“路由器管理它提供的可观测数据并本地化订阅。当组件被破坏时,订阅将被清理,以防止内存泄漏,因此我们不需要从可观测的路由参数中取消订阅。”--马克·拉伊科克

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

    资料来源4

    在这段来自NgEurope的视频中,Rob Wormald还说,你不需要退订路由器可观察到的内容。在2016年11月的这段视频中,他还提到了HTTP服务和ActivatedRoute.params

    TLDR:

    对于这个问题,有(2)种可观测--有限值和无限值。

    HTTPObservables生成有限(1)值,而DOM事件侦听器Observables生成无限值。

    如果手动调用subscribe(不使用异步管道),则从infiniteObservables中取消订阅。

    不用担心有限的,rxjs会处理它们。

    来源1

    我在Angular的Gitter找到了Rob Wormald的答案。

    他说(我为了清晰和强调而重新组织)

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

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

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

    他还在youtube关于可观察到的视频中提到,他们会自己清理...在完成的可观察到的情况下(比如承诺,承诺总是完成的,因为它们总是产生1个值并结束--我们从不担心取消对承诺的订阅,以确保它们清理了XHR事件监听器,对吗?)。

    来源2

    在Angular 2的Rangle指南中还写着

    在大多数情况下,我们不需要显式调用unsubscribe方法,除非我们希望提前取消,或者我们的Observable具有比订阅更长的生命周期。可观察运算符的默认行为是,一旦发布了.complete()或.error()消息,就立即处理订阅。请记住,RxJS的设计是为了在大多数情况下以“点燃并忘记”的方式使用。

    短语our Observable的寿命比我们的subscription什么时候适用?

    当在组件内创建订阅时,该订阅在observable完成之前(或在完成之前不长时间)被销毁时,它适用。

    我认为这意味着,如果我们订阅了一个http请求或一个发出10个值的observable,并且我们的组件在http请求返回或发出10个值之前被销毁,那么我们仍然可以!

    当请求返回或第10个值最终发出时,observable将完成,所有资源都将被清理。

    来源3

    如果我们从同一Rangle guide中查看这个示例,我们可以看到subscriptionroute.params确实需要unsubscribe(),因为我们不知道这些params何时将停止更改(发出新值)。

    该组件可能通过导航离开而被破坏,在这种情况下,路由参数可能仍在更改(技术上它们可能在应用程序结束之前更改),并且订阅中分配的资源仍将被分配,因为没有complety

  •  类似资料:
    • 我使用SockJS和StompJS,当我在浏览器中打开我的应用程序时,有时它会在连接到websocket之前尝试订阅一些主题。我希望主题订阅等待应用程序连接到websocket。 这就是我实现此代码的原因,我将其称为: 因此,我只在连接状态为时才订阅该主题,并且只有在客户端首次成功连接时才会调用该主题。 我想稍后从主题中取消订阅,所以我需要内部订阅返回的对象,我还需要内部订阅的消息。 我所实现的很

    • 什么是 Subscription ? - Subscription 是表示可清理资源的对象,通常是 Observable 的执行。Subscription 有一个重要的方法,即 unsubscribe,它不需要任何参数,只是用来清理由 Subscription 占用的资源。在上一个版本的 RxJS 中,Subscription 叫做 "Disposable" (可清理对象)。 var observ

    • 我通读了RxJS文档,并希望确保我理解了< code > subscriber . unsubscribe()和< code > subscriber . complete()之间的区别。 假设我有一个有两个订阅者的可观察对象,订阅者1和订阅者2。如果订阅者1对其订阅调用取消订阅,它将不再接收来自可观察对象的通知,但订阅者2将继续接收它们。 <代码>的文档。complete(): 观察者回调,用于

    • 我对运算符和只是链接有点困惑。下面的两个例子在功能上是等同的吗?管道功能的目的或优势是什么?

    • 例如: 我是否需要取消订阅此订阅?我问这是因为我不知道Angular是否处理它本身。另外,请求是一次性的,而不是连续的。我倾向于认为我不需要。你有什么想法? 根据下面的帖子:Angular/RXJS,我什么时候应该取消订阅'subscription' RxJS处理一次性订阅,但我在他们的文档中没有找到任何东西。

    • 我有一个Angular组件,将observable(BehaviourSubject)设置为类成员 有时我会订阅它 问题是:我是否应该在ngOnDestroy()中取消订阅 尚不清楚,因为可观察的生命周期似乎与组件的生命周期相同,可能我们不应该关心内存泄漏。 示例代码: