914

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

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

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

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

同时路线和导航指南说:

最终,我们将导航到其他地方。路由器将从 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();
}
4

26 回答 26

1157

TL;博士

对于这个问题,有两种 Observables -有限值和无限值。

httpObservable 产生有限(1) 值,而 DOM 事件侦听器 Observable 产生无限值。

如果您手动调用subscribe(不使用异步管道),unsubscribe则从无限Observables 中调用。

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


资料来源:

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

    他说(为了清楚起见,我进行了重新组织,重点是我的):

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

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

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

    他还在这段关于 Observables的YouTube 视频中提到, “它们会自行清理......”在完成的 Observables 的上下文中(比如 Promises,它总是完成的,因为它们总是产生一个值并结束——我们从不担心退订承诺确保他们清理 XHR 事件侦听器,对吗?)

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

    在大多数情况下,我们不需要显式调用该unsubscribe方法,除非我们想提前取消或者我们Observable的生命周期比我们的订阅更长。运营商的默认行为是在发布消息后立即处理Observable订阅。请记住,RxJS 被设计为大多数时候以“即发即弃”的方式使用。.complete().error()

    “我们Observable的寿命比订阅的寿命长”这句话何时适用?

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

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

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

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

    该组件可以通过导航而被破坏,在这种情况下,路由参数可能仍然会发生变化(从技术上讲,它们可能会在应用程序结束之前发生变化)并且订阅中分配的资源仍然会被分配,因为没有完成

  4. 在NgEurope 的这段视频中,Rob Wormald 还说您不需要取消订阅 Router Observables。他还在2016 年 11 月的这段视频中提到了这项http服务。ActivatedRoute.params

  5. Angular 教程,路由章节现在声明如下:

    管理它提供的Router可观察对象并本地化订阅。当组件被销毁时,订阅会被清理,防止内存泄漏,所以我们不需要从路由中取消订阅params Observable

    Here's a Discussion on the GitHub Issues for the Angular docs about Router Observables 其中 Ward Bell 提到对所有这些的澄清正在进行中。


我在 NGConf 上就这个问题与 Ward Bell 进行了交谈(我什至向他展示了这个答案,他说是正确的),但他告诉我 Angular 的文档团队有一个未发表的问题的解决方案(尽管他们正在努力让它获得批准)。他还告诉我,我可以用即将发布的官方推荐来更新我的 SO 答案。

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

然后我们调用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();
    }
}

注意:将操作符添加到最后一个很重要,takeUntil以防止操作符链中的中间 Observable 泄漏。


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

Ward 提到“现在有一个可怕的 takeUntil 舞蹈,需要大量机器”,Shai Reznik 提到“Angular 处理一些订阅,如 http 和路由”

作为回应,Ben 提到现在正在讨论允许 Observables 挂钩到 Angular 组件生命周期事件中,Ward 建议使用一个生命周期事件的 Observable,组件可以订阅它作为了解何时完成 Observables 维护的一种方式作为组件内部状态。

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

  1. 来自 RxJs 核心团队成员 Nicholas Jamieson的模式建议takeUntil()和 TSLint 规则来帮助执行它:https ://ncjamieson.com/avoiding-takeuntil-leaks/

  2. 轻量级 npm 包,它公开了一个 Observable 运算符,该运算符将组件实例 ( this) 作为参数并在此期间自动取消订阅ngOnDestroyhttps ://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()可能表明您的组件试图做太多事情,应该考虑将现有组件分为功能组件和演示组件。然后,您可以| async将功能组件中的 Observable 转换Input为 Presentational 组件,这意味着在任何地方都不需要订阅。在此处阅读有关此方法的更多信息。

于 2016-12-16T04:11:32.177 回答
124

您无需拥有大量订阅并手动取消订阅。使用SubjecttakeUntil组合来像老板一样处理订阅:

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。您可能更喜欢它,但请注意,这样您的 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 */ })
  }

  ngOnDestroy() {
    this.alive = false
  }
}
于 2017-03-09T12:35:47.500 回答
107

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();
  }
}
于 2017-05-03T12:00:01.747 回答
42

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

引用自Routing & Navigation

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

有一些特殊的 observables 是不必要的。ActivatedRoute 可观察对象属于例外。

ActivatedRoute 及其 observables 与 Router 本身是绝缘的。当不再需要路由组件并且注入的 ActivatedRoute 随之消亡时,Router 会销毁路由组件。

无论如何,请随时取消订阅。这是无害的,绝不是坏习惯。

并回应以下链接:

我收集了一些关于 Angular 组件中的 observables 取消订阅的最佳实践与您分享:

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

    注意:关于作用域服务,即组件提供者,它们在组件被销毁时被销毁。在这种情况下,如果我们订阅了这个提供者中的任何 observable,我们应该考虑使用OnDestroy生命周期钩子取消订阅,该钩子将在服务被销毁时调用,根据文档。
  • 使用抽象技术来避免可能因取消订阅而导致的任何代码混乱。takeUntil 您可以使用(3)管理您的订阅,也可以使用(4) 在 Angular 中取消订阅 Observables 的最简单方法中提到的这个npm
  • 总是退订像和这样的FormGroup可观察对象form.valueChangesform.statusChanges
  • 始终取消订阅可观察的Renderer2服务,例如renderer2.listen
  • 取消订阅所有其他可观察对象作为内存泄漏防护步骤,直到 Angular 文档明确告诉我们哪些可观察对象不需要取消订阅(检查问题:(5)RxJS 取消订阅(打开)文档)。
  • 奖励:始终使用 Angular 方法来绑定事件,HostListener因为 Angular 非常关心在需要时删除事件侦听器,并防止由于事件绑定导致的任何潜在内存泄漏。

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

于 2018-08-07T18:03:24.013 回答
19

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

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

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

_xhr.addEventListener('load', onLoad);
_xhr.addEventListener('error', onError);

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

_xhr.removeEventListener('load', onLoad);
_xhr.removeEventListener('error', onError);
_xhr.abort();

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

通常,这足以证明需要手动unsubscribe()调用。但是根据这个WHATWG 规范XMLHttpRequest()一旦“完成”,它就会受到垃圾收集的影响,即使它附加了事件侦听器。所以我想这就是为什么 Angular 2 官方指南省略unsubscribe()并让 GC 清理听众的原因。

至于你的第二个例子,它取决于params. 到今天为止,Angular 官方指南不再显示取消订阅params. 我再次查看了src,发现它params只是一个BehaviorSubject。由于没有使用事件侦听器或计时器,也没有创建全局变量,因此省略 应该是安全的unsubscribe()

您的问题的底线是始终调用unsubscribe()以防止内存泄漏,除非您确定 observable 的执行不会创建全局变量、添加事件侦听器、设置计时器或执行任何其他导致内存泄漏的操作.

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

于 2016-12-01T07:09:34.530 回答
9

Angular 2 官方文档解释了何时取消订阅以及何时可以安全地忽略它。看看这个链接:

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

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

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

我们没有将这个守卫添加到 MissionControlComponent 中,因为作为父级,它控制着 MissionService 的生命周期。

我希望这可以帮助你。

于 2016-06-29T11:08:04.773 回答
6

基于:使用类继承挂钩到 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
  }

}

并使用:

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

于 2017-04-12T11:04:38.563 回答
4

官方编辑#3 答案(和变体)效果很好,但让我感到困惑的是围绕可观察订阅的业务逻辑的“混乱”。

这是使用包装器的另一种方法。

警告:实验代码

文件subscribeAndGuard.ts用于创建一个新的 Observable 扩展来 wrap.subscribe()并在其中 wrap ngOnDestroy()
用法与 相同.subscribe(),除了引用组件的附加第一个参数。

import { Observable } from 'rxjs/Observable';
import { Subscription } from 'rxjs/Subscription';

const subscribeAndGuard = function(component, fnData, fnError = null, fnComplete = null) {

  // Define the subscription
  const sub: Subscription = this.subscribe(fnData, fnError, fnComplete);

  // Wrap component's onDestroy
  if (!component.ngOnDestroy) {
    throw new Error('To use subscribeAndGuard, the component must implement ngOnDestroy');
  }
  const saved_OnDestroy = component.ngOnDestroy;
  component.ngOnDestroy = () => {
    console.log('subscribeAndGuard.onDestroy');
    sub.unsubscribe();
    // Note: need to put original back in place
    // otherwise 'this' is undefined in component.ngOnDestroy
    component.ngOnDestroy = saved_OnDestroy;
    component.ngOnDestroy();

  };

  return sub;
};

// Create an Observable extension
Observable.prototype.subscribeAndGuard = subscribeAndGuard;

// Ref: https://www.typescriptlang.org/docs/handbook/declaration-merging.html
declare module 'rxjs/Observable' {
  interface Observable<T> {
    subscribeAndGuard: typeof subscribeAndGuard;
  }
}

这是一个有两个订阅的组件,一个有包装,一个没有。唯一需要注意的是它必须实现 OnDestroy(如果需要,可以使用空主体),否则 Angular 不知道调用包装版本。

import { Component, OnInit, OnDestroy } from '@angular/core';
import { Observable } from 'rxjs/Observable';
import 'rxjs/Rx';
import './subscribeAndGuard';

@Component({
  selector: 'app-subscribing',
  template: '<h3>Subscribing component is active</h3>',
})
export class SubscribingComponent implements OnInit, OnDestroy {

  ngOnInit() {

    // This subscription will be terminated after onDestroy
    Observable.interval(1000)
      .subscribeAndGuard(this,
        (data) => { console.log('Guarded:', data); },
        (error) => { },
        (/*completed*/) => { }
      );

    // This subscription will continue after onDestroy
    Observable.interval(1000)
      .subscribe(
        (data) => { console.log('Unguarded:', data); },
        (error) => { },
        (/*completed*/) => { }
      );
  }

  ngOnDestroy() {
    console.log('SubscribingComponent.OnDestroy');
  }
}

一个演示 plunker 在这里

附加说明: 重新编辑 3 - “官方”解决方案,这可以通过在订阅前使用 takeWhile() 而不是 takeUntil() 来简化,并在 ngOnDestroy 中使用一个简单的布尔值而不是另一个 Observable。

@Component({...})
export class SubscribingComponent implements OnInit, OnDestroy {

  iAmAlive = true;
  ngOnInit() {

    Observable.interval(1000)
      .takeWhile(() => { return this.iAmAlive; })
      .subscribe((data) => { console.log(data); });
  }

  ngOnDestroy() {
    this.iAmAlive = false;
  }
}
于 2017-05-16T23:06:53.617 回答
4

由于 seangwright 的解决方案(编辑 3)似乎非常有用,我也发现将这个功能打包到基础组件中很痛苦,并提示其他项目团队记住在 ngOnDestroy 上调用 super() 来激活这个功能。

这个答案提供了一种摆脱超级调用的方法,并使“co​​mponentDestroyed$”成为基础组件的核心。

class BaseClass {
    protected componentDestroyed$: Subject<void> = new Subject<void>();
    constructor() {

        /// wrap the ngOnDestroy to be an Observable. and set free from calling super() on ngOnDestroy.
        let _$ = this.ngOnDestroy;
        this.ngOnDestroy = () => {
            this.componentDestroyed$.next();
            this.componentDestroyed$.complete();
            _$();
        }
    }

    /// placeholder of ngOnDestroy. no need to do super() call of extended class.
    ngOnDestroy() {}
}

然后您可以自由使用此功能,例如:

@Component({
    selector: 'my-thing',
    templateUrl: './my-thing.component.html'
})
export class MyThingComponent extends BaseClass implements OnInit, OnDestroy {
    constructor(
        private myThingService: MyThingService,
    ) { super(); }

    ngOnInit() {
        this.myThingService.getThings()
            .takeUntil(this.componentDestroyed$)
            .subscribe(things => console.log(things));
    }

    /// optional. not a requirement to implement OnDestroy
    ngOnDestroy() {
        console.log('everything works as intended with or without super call');
    }

}
于 2017-04-24T04:30:41.773 回答
4

对于在发出结果后直接完成的可观察AsyncSubject对象,例如来自 http 请求的可观察对象,因此您无需取消订阅。调用这些并没有什么坏处unsubscribe(),但如果 observable 是closedunsubscribe 方法将根本不做任何事情

if (this.closed) {
  return;
}

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

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

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

第一个管道的另一个优点是您可以传递一个谓词,该谓词将帮助您返回满足某些条件的第一个值:

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

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

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

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

observable.pipe(single()).subscribe(observer);

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

第一的

将在第一个项目出现时立即发出。之后会马上完成。

单身的

如果源 observable 发出多个事件将失败。


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

于 2020-03-01T09:33:10.797 回答
3

根据@seangwright的回答,我编写了一个抽象类来处理组件中的“无限”可观察对象的订阅:

import { OnDestroy } from '@angular/core';
import { Subscription } from 'rxjs/Subscription';
import { Subject } from 'rxjs/Subject';
import { Observable } from 'rxjs/Observable';
import { PartialObserver } from 'rxjs/Observer';

export abstract class InfiniteSubscriberComponent implements OnDestroy {
  private onDestroySource: Subject<any> = new Subject();

  constructor() {}

  subscribe(observable: Observable<any>): Subscription;

  subscribe(
    observable: Observable<any>,
    observer: PartialObserver<any>
  ): Subscription;

  subscribe(
    observable: Observable<any>,
    next?: (value: any) => void,
    error?: (error: any) => void,
    complete?: () => void
  ): Subscription;

  subscribe(observable: Observable<any>, ...subscribeArgs): Subscription {
    return observable
      .takeUntil(this.onDestroySource)
      .subscribe(...subscribeArgs);
  }

  ngOnDestroy() {
    this.onDestroySource.next();
    this.onDestroySource.complete();
  }
}

要使用它,只需在您的角度组件中扩展它并调用subscribe()如下方法:

this.subscribe(someObservable, data => doSomething());

它还像往常一样接受错误和完成回调,观察者对象,或者根本不接受回调。super.ngOnDestroy()如果您也在子组件中实现该方法,请记住调用。

在这里找到 Ben Lesh 的附加参考:RxJS:不要取消订阅

于 2018-04-19T23:34:36.237 回答
3

Subscription 本质上只有一个 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()
    }
}

我们将 ngOnDestroy 添加到 AppCompoennt 并在 this.subscription Observable 上调用 unsubscribe 方法

如果有多个订阅:

@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()
    }
}
于 2020-07-23T09:56:11.903 回答
2

上述情况的另一个简短补充是:

  • 始终取消订阅,当订阅流中的新值不再需要或无关紧要时,这将导致触发器数量减少并在少数情况下提高性能。订阅的数据/事件不再存在或需要对全新流的新订阅(刷新等)的组件等情况是取消订阅的一个很好的例子。
于 2018-06-16T12:49:59.953 回答
2

I tried seangwright's solution (Edit 3)

That is not working for Observable that created by timer or interval.

However, i got it working by using another approach:

import { Component, OnDestroy, OnInit } from '@angular/core';
import 'rxjs/add/operator/takeUntil';
import { Subject } from 'rxjs/Subject';
import { Subscription } from 'rxjs/Subscription';
import 'rxjs/Rx';

import { MyThingService } from '../my-thing.service';

@Component({
   selector: 'my-thing',
   templateUrl: './my-thing.component.html'
})
export class MyThingComponent implements OnDestroy, OnInit {
   private subscriptions: Array<Subscription> = [];

  constructor(
     private myThingService: MyThingService,
   ) { }

  ngOnInit() {
    const newSubs = this.myThingService.getThings()
        .subscribe(things => console.log(things));
    this.subscriptions.push(newSubs);
  }

  ngOnDestroy() {
    for (const subs of this.subscriptions) {
      subs.unsubscribe();
   }
 }
}
于 2017-04-11T20:00:48.980 回答
2

如果需要取消订阅,可以使用以下可观察管道方法的运算符

import { Observable, Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import { OnDestroy } from '@angular/core';

export const takeUntilDestroyed = (componentInstance: OnDestroy) => <T>(observable: Observable<T>) => {
  const subjectPropertyName = '__takeUntilDestroySubject__';
  const originalOnDestroy = componentInstance.ngOnDestroy;
  const componentSubject = componentInstance[subjectPropertyName] as Subject<any> || new Subject();

  componentInstance.ngOnDestroy = (...args) => {
    originalOnDestroy.apply(componentInstance, args);
    componentSubject.next(true);
    componentSubject.complete();
  };

  return observable.pipe(takeUntil<T>(componentSubject));
};

它可以这样使用:

import { Component, OnDestroy, OnInit } from '@angular/core';
import { Observable } from 'rxjs';

@Component({ template: '<div></div>' })
export class SomeComponent implements OnInit, OnDestroy {

  ngOnInit(): void {
    const observable = Observable.create(observer => {
      observer.next('Hello');
    });

    observable
      .pipe(takeUntilDestroyed(this))
      .subscribe(val => console.log(val));
  }

  ngOnDestroy(): void {
  }
}

操作符封装了组件的 ngOnDestroy 方法。

重要提示:操作员应该是可观察管道中的最后一个操作员。

于 2019-02-20T15:07:51.313 回答
2

我喜欢最后两个答案,但"this"如果ngOnDestroy.

我把它改成了这个,看起来它解决了这个问题。

export abstract class BaseComponent implements OnDestroy {
    protected componentDestroyed$: Subject<boolean>;
    constructor() {
        this.componentDestroyed$ = new Subject<boolean>();
        let f = this.ngOnDestroy;
        this.ngOnDestroy = function()  {
            // without this I was getting an error if the subclass had
            // this.blah() in ngOnDestroy
            f.bind(this)();
            this.componentDestroyed$.next(true);
            this.componentDestroyed$.complete();
        };
    }
    /// placeholder of ngOnDestroy. no need to do super() call of extended class.
    ngOnDestroy() {}
}
于 2017-04-26T17:58:33.190 回答
2

当组件被销毁时,您通常需要取消订阅,但随着我们的进行,Angular 会越来越多地处理它,例如在新的 Angular4 次要版本中,他们有这个部分用于路由取消订阅:

您需要退订吗?

如 ActivatedRoute:Routing & Navigation 页面的路线信息一站式商店部分所述,Router 管理它提供的 observables 并本地化订阅。当组件被销毁时,订阅会被清理,以防止内存泄漏,因此您不需要从路由 paramMap Observable 取消订阅。

下面的例子也是 Angular 创建组件并在之后销毁它的一个很好的例子,看看组件是如何实现 OnDestroy 的,如果你需要 onInit,你也可以在你的组件中实现它,比如 implementsOnInit, OnDestroy

import { Component, Input, OnDestroy } from '@angular/core';  
import { MissionService } from './mission.service';
import { Subscription }   from 'rxjs/Subscription';

@Component({
  selector: 'my-astronaut',
  template: `
    <p>
      {{astronaut}}: <strong>{{mission}}</strong>
      <button
        (click)="confirm()"
        [disabled]="!announced || confirmed">
        Confirm
      </button>
    </p>
  `
})

export class AstronautComponent implements OnDestroy {
  @Input() astronaut: string;
  mission = '<no mission announced>';
  confirmed = false;
  announced = false;
  subscription: Subscription;

  constructor(private missionService: MissionService) {
    this.subscription = missionService.missionAnnounced$.subscribe(
      mission => {
        this.mission = mission;
        this.announced = true;
        this.confirmed = false;
    });
  }

  confirm() {
    this.confirmed = true;
    this.missionService.confirmMission(this.astronaut);
  }

  ngOnDestroy() {
    // prevent memory leak when component destroyed
    this.subscription.unsubscribe();
  }
}
于 2017-10-16T11:52:57.567 回答
1

SubSink 包,一个简单且一致的退订解决方案

由于没有其他人提到它,我想推荐 Ward Bell 创建的 Subsink 包:https ://github.com/wardbell/subsink#readme 。

我一直在一个项目中使用它,我们是几个开发人员都在使用它。有一种适用于各种情况的一致方式非常有帮助。

于 2020-02-02T08:59:13.007 回答
1

为了处理订阅,我使用“Unsubscriber”类。

这是取消订阅者类。

export class Unsubscriber implements OnDestroy {
  private subscriptions: Subscription[] = [];

  addSubscription(subscription: Subscription | Subscription[]) {
    if (Array.isArray(subscription)) {
      this.subscriptions.push(...subscription);
    } else {
      this.subscriptions.push(subscription);
    }
  }

  unsubscribe() {
    this.subscriptions
      .filter(subscription => subscription)
      .forEach(subscription => {
        subscription.unsubscribe();
      });
  }

  ngOnDestroy() {
    this.unsubscribe();
  }
}

您可以在任何组件/服务/效果等中使用此类。

例子:

class SampleComponent extends Unsubscriber {
    constructor () {
        super();
    }

    this.addSubscription(subscription);
}
于 2019-05-20T10:33:05.867 回答
1

ngOnDestroy函数 (angular lifeCycle) 的 SPA 应用程序中,对于每个订阅,您都需要取消订阅。优势 => 以防止状态变得太重。

例如:在组件 1 中:

import {UserService} from './user.service';

private user = {name: 'test', id: 1}

constructor(public userService: UserService) {
    this.userService.onUserChange.next(this.user);
}

在役:

import {BehaviorSubject} from 'rxjs/BehaviorSubject';

public onUserChange: BehaviorSubject<any> = new BehaviorSubject({});

在组件 2 中:

import {Subscription} from 'rxjs/Subscription';
import {UserService} from './user.service';

private onUserChange: Subscription;

constructor(public userService: UserService) {
    this.onUserChange = this.userService.onUserChange.subscribe(user => {
        console.log(user);
    });
}

public ngOnDestroy(): void {
    // note: Here you have to be sure to unsubscribe to the subscribe item!
    this.onUserChange.unsubscribe();
}
于 2019-04-19T04:50:17.227 回答
0

这是我对这个问题的看法,为了让我的生活保持简单,我选择了在组件被销毁时取消订阅的手动方式。

为此,我创建了一个名为 Subscriptor 的类,它主要包含静态成员,即:

  • 一个私有变量订阅 - 它包含所有提供的订阅
  • 订阅设置器 - 将每个新订阅推送到订阅数组
  • 取消订阅方法 - 如果已定义,则取消订阅订阅数组包含的每个订阅,并清空订阅数组

下标.ts

import { Subscription } from "rxjs";

export class Subscriptor {
    private static subscriptions: Subscription[] = [];

    static set subscription(subscription: Subscription) {
        Subscriptor.subscriptions.push(subscription);
    }

    static unsubscribe() {
        Subscriptor.subscriptions.forEach(subscription => subscription ? subscription.unsubscribe() : 0);
        Subscriptor.subscriptions = [];
    }
}

组件内部的用法如下:

当您想订阅任何服务时,只需将订阅放到 Subscriptor 的 setter 中即可。

ngOnInit(): void {
    Subscriptor.subscription = this.userService.getAll().subscribe(users => this.users = users);
    Subscriptor.subscription = this.categoryService.getAll().subscribe(categories => this.categories = categories);
    Subscriptor.subscription = this.postService.getAll().subscribe(posts => this.posts = posts);
}

当您想取消订阅任何服务时,只需调用 Subscriptor 的取消订阅方法即可。

ngOnDestroy(): void {
    Subscriptor.unsubscribe();
}
于 2021-03-22T20:03:28.933 回答
0

你可以使用最新Subscription的类来取消订阅 Observable 而不是那么混乱的代码。

我们可以这样做,normal variable但它会出现在每个新订阅上,因此请避免这种情况,当您处理更多数量的 Obseravables和override the last subscriptionObservables 的类型时,这种方法非常有用BehavoiurSubjectSubject

订阅

表示一次性资源,例如 Observable 的执行。订阅有一个重要的方法,取消订阅,它不接受任何参数,只是释放订阅持有的资源。

你可以通过两种方式使用它,

  • 可以直接将订阅推送到订阅数组

     subscriptions:Subscription[] = [];
    
     ngOnInit(): void {
    
       this.subscription.push(this.dataService.getMessageTracker().subscribe((param: any) => {
                //...  
       }));
    
       this.subscription.push(this.dataService.getFileTracker().subscribe((param: any) => {
            //...
        }));
     }
    
     ngOnDestroy(){
        // prevent memory leak when component destroyed
        this.subscriptions.forEach(s => s.unsubscribe());
      }
    
  • 使用add()_Subscription

    subscriptions = new Subscription();
    
    this.subscriptions.add(subscribeOne);
    this.subscriptions.add(subscribeTwo);
    
    ngOnDestroy() {
      this.subscriptions.unsubscribe();
    }
    

ASubscription可以持有子订阅并安全地取消订阅它们。此方法处理可能的错误(例如,如果任何子订阅为空)。

希望这可以帮助.. :)

于 2019-09-09T07:15:26.797 回答
0

出于性能原因,始终建议从您的可观察订阅中取消订阅以避免内存泄漏,并且有不同的方法可以做到这一点,

顺便说一句,我阅读了大部分答案,但我没有找到有人在谈论async管道,这是RxjsAngular 应用程序的推荐模式,因为它在离开将被销毁的组件时自动提供订阅和订阅:

请找到一个如何实施的例子

app.compoent.ts

import { Component, OnInit } from '@angular/core';
import { Observable } from 'rxjs';

import { BookService } from './book.service';
import { Book } from './book';

@Component({
   selector: 'app-observable',
   templateUrl: './observable.component.html'
})
export class AppComponent implements OnInit { 
   books$: Observable<Book[]>
   constructor(private bookService: BookService) { }
   ngOnInit(): void {
        this.books$ = this.bookService.getBooksWithObservable();
   }
} 

app.compoent.html

<h3>AsyncPipe with Promise using NgFor</h3>
<ul>
  <li *ngFor="let book of books$ | async" >
    Id: {{book?.id}}, Name: {{book?.name}}
  </li>
</ul>
于 2021-07-13T22:32:19.923 回答
0

这里有很多很棒的答案...

让我添加另一种选择:

import { interval    } from "rxjs";
import { takeUntil   } from "rxjs/operators";
import { Component   } from "@angular/core";
import { Destroyable } from "@bespunky/angular-zen/core";

@Component({
    selector: 'app-no-leak-demo',
    template: ' Destroyable component rendered. Unload me and watch me cleanup...'
})
export class NoLeakComponent extends Destroyable
{
    constructor()
    {
        super();

        this.subscribeToInterval();
    }

    private subscribeToInterval(): void
    {
        const value    = interval(1000);
        const observer = {
            next    : value => console.log(` Destroyable: ${value}`),
            complete: ()    => console.log(' Observable completed.')
        };

        // ==== Comment one and uncomment the other to see the difference ====
        
        // Subscribe using the inherited subscribe method
         this.subscribe(value, observer);

        // ... or pipe-in the inherited destroyed subject
        //value.pipe(takeUntil(this.destroyed)).subscribe(observer);
    }
}

现场示例

这里发生了什么事

组件/服务扩展Destroyable(来自名为 的库@bespunky/angular-zen)。

该类现在可以简单地使用this.subscribe()takeUntil(this.destroyed)不使用任何额外的样板代码。

要安装库,请使用:
> npm install @bespunky/angular-zen

于 2021-02-25T18:49:41.730 回答
0

就我而言,我正在使用@seanwright 提出的解决方案的变体:
https ://github.com/NetanelBasal/ngx-take-until-destroy

它是ngx-rocket / starter-kit项目中使用的文件。你可以在这里访问它until-destroyed.ts

该组件看起来像这样

/**
 * RxJS operator that unsubscribe from observables on destory.
 * Code forked from https://github.com/NetanelBasal/ngx-take-until-destroy
 *
 * IMPORTANT: Add the `untilDestroyed` operator as the last one to
 * prevent leaks with intermediate observables in the
 * operator chain.
 *
 * @param instance The parent Angular component or object instance.
 * @param destroyMethodName The method to hook on (default: 'ngOnDestroy').
 */
import { untilDestroyed } from '../../core/until-destroyed';

@Component({
  selector: 'app-example',
  templateUrl: './example.component.html'
})
export class ExampleComponent implements OnInit, OnDestroy {

  ngOnInit() {
    interval(1000)
        .pipe(untilDestroyed(this))
        .subscribe(val => console.log(val));

    // ...
  }


  // This method must be present, even if empty.
  ngOnDestroy() {
    // To protect you, an error will be thrown if it doesn't exist.
  }
}
于 2021-01-06T19:27:55.700 回答
-1

--- 更新 Angular 9 和 Rxjs 6 解决方案

  1. unsubscribengDestroyAngular 组件的生命周期中使用
class SampleComponent implements OnInit, OnDestroy {
  private subscriptions: Subscription;
  private sampleObservable$: Observable<any>;

  constructor () {}

  ngOnInit(){
    this.subscriptions = this.sampleObservable$.subscribe( ... );
  }

  ngOnDestroy() {
    this.subscriptions.unsubscribe();
  }
}
  1. takeUntil在 Rxjs 中使用
class SampleComponent implements OnInit, OnDestroy {
  private unsubscribe$: new Subject<void>;
  private sampleObservable$: Observable<any>;

  constructor () {}

  ngOnInit(){
    this.subscriptions = this.sampleObservable$
    .pipe(takeUntil(this.unsubscribe$))
    .subscribe( ... );
  }

  ngOnDestroy() {
    this.unsubscribe$.next();
    this.unsubscribe$.complete();
  }
}
  1. 对于您调用的某些操作,ngOnInit仅在组件初始化时发生一次。
class SampleComponent implements OnInit {

  private sampleObservable$: Observable<any>;

  constructor () {}

  ngOnInit(){
    this.subscriptions = this.sampleObservable$
    .pipe(take(1))
    .subscribe( ... );
  }
}

我们也有async管道。但是,这个在模板上使用(不在 Angular 组件中)。

于 2020-04-27T06:19:09.010 回答