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

时间:2016-06-24 07:52:46

标签: angular rxjs observable subscription angular-component-life-cycle

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

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

HTTP Client Guide忽略这样的订阅:

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

同时Route & Navigation Guide说:

  

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

     

我们在Observable方法中取消订阅ngOnDestroy

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

26 个答案:

答案 0 :(得分:863)

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

最近一集Adventures in 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. 来自RxJs核心团队成员Nicholas Jamieson的takeUntil()模式建议以及帮助实施该模式的tslint规则。 https://blog.angularindepth.com/rxjs-avoiding-takeuntil-leaks-fb5182d047ef

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

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

  4. 自定义指令*ngSubscribe,其工作方式与异步管道类似,但在模板中创建了一个嵌入式视图,因此您可以参考“已解包”&#39;整个模板的价值。 https://netbasal.com/diy-subscription-handling-directive-in-angular-c8f6e762697f

  5. 我在评论中提到尼古拉斯&#39;博客过度使用takeUntil()可能表示您的组件尝试执行过多操作,并且将现有组件分为功能演示组件被考虑。然后,您可以将功能组件中的Observable | async转换为演示组件的Input,这意味着在任何地方都不需要订阅。阅读有关此方法的更多信息here

    ---编辑3 - &#39;官方&#39;解决方案(2017/04/09)

    我在NGConf与Ward Bell谈到了这个问题(我甚至向他展示了这个答案,他说这是正确的)但是他告诉我Angular的文档团队解决了这个未发表的问题(虽然他们正在研究获得批准)。他还告诉我,我可以通过即将发布的官方建议来更新我的答案。

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

    然后,我们在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泄漏。

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

    来源5

    Angular教程,路由章节现在陈述如下:&#34;路由器管理它提供的可观察量并本地化订阅。当组件被销毁时,清理订阅,防止内存泄漏,因此我们不需要取消订阅路径参数Observable。&#34; - Mark Rajcok

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

    ---编辑1

    来源4

    在此video from NgEurope中,Rob Wormald还表示您无需取消订阅Router Observables。他还在此video from November 2016中提到http服务和ActivatedRoute.params

    ---原始答案

    <强> TLDR:

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

    http Observables生成有限(1)值,类似DOM event listener Observables生成无限值。

    如果您手动拨打subscribe(不使用异步管道),则来自无限 unsubscribe的{​​{1}}。

    不要担心有限Observables会照顾它们。

    来源1

    我在Angular的Gitter here中找到了Rob Wormald的回答。

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

      

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

         

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

         

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

    他还在youtube video中提到了RxJs {Oberables} they clean up after themselves ......在Observables的上下文中complete(就像Promises一样,因为它们总是产生1个值,所以总是完成结束 - 我们从不担心取消订阅Promises以确保他们清理xhr事件监听器,对吧?)。

    来源2

    同样在Rangle guide to Angular 2中读取

      

    在大多数情况下,我们不需要显式调用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指南中查看this example,我们可以看到Subscriptionroute.params确实需要unsubscribe(),因为我们不知道何时那些params将停止更改(发出新值)。

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

答案 1 :(得分:71)

您不需要手动进行大量订阅和取消订阅。使用RxJS.SubjecttakeUntil组合来处理像老板这样的订阅:

import {Subject} from "rxjs/Subject";

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

    constructor(protected titleService: TitleService)
    {
        this.titleService.emitter1$
            .takeUntil(this.componentDestroyed$)
            .subscribe(
            (data: any) =>
            {
                // ... do something 1
            }
        );

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

        // ...

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

    ngOnDestroy()
    {
        this.componentDestroyed$.next(true);
        this.componentDestroyed$.complete();
    }
}
建议by @acumartini in comments提出的

替代方法使用takeWhile代替takeUntil。您可能更喜欢它,但请注意,这样您的Observable执行将不会在组件的ngDestroy上被取消(例如,当您耗费时间计算或等待来自服务器的数据时)。基于takeUntil的方法没有这个缺点,导致立即取消请求。 Thanks to @AlexChe for detailed explanation in comments

所以这是代码:

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

    constructor(protected titleService: TitleService)
    {
        this.titleService.emitter1$
            .takeWhile(() => this.alive)
            .subscribe(
            (data: any) =>
            {
                // ... do something 1
            }
        );

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

        // ...

        this.titleService.emitterN$
            .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;
    }
}

答案 2 :(得分:56)

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

答案 3 :(得分:23)

一些有关在Angular组件中取消可观察项的最佳实践:

引用Routing & Navigation

  

订阅组件中的可观察对象时,几乎总是安排在组件被破坏时退订。

     

有一些不需要观察的例外观察点。可观察到的ActivatedRoute除外。

     

ActivatedRoute及其可观察物与路由器本身绝缘。当不再需要路由组件并且注入的ActivatedRoute随之死亡时,Router会销毁它。

     

随时可以退订。这是无害的,绝不是坏习惯。

并响应以下链接:

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

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

    注意:关于范围服务,即组件提供程序,在销毁组件时会销毁它们。在这种情况下,如果我们订阅此提供程序内部的任何可观察对象,则应考虑使用OnDestroy生命周期钩子取消订阅该生命周期钩子,根据文档,该钩子将在服务被销毁时调用。
  • 使用抽象技术来避免因取消订阅而导致的任何代码混乱。您可以使用takeUntil (3)管理订阅,也可以使用package中提到的npm (4) The easiest way to unsubscribe from Observables in Angular
  • 始终取消订阅FormGroupform.valueChanges之类的form.statusChanges
  • 始终取消订阅Renderer2之类的renderer2.listen服务的可观察对象
  • 从其他可观察对象中退订作为内存泄漏保护措施,直到Angular Docs明确告知我们不需要取消哪些可观察对象(检查问题:(5) Documentation for RxJS Unsubscribing (Open))。
  • 奖金:始终使用Angular方式绑定HostListener之类的事件,因为angular会在需要时非常注意删除事件侦听器,并防止由于事件绑定而导致任何潜在的内存泄漏。

一个不错的最终提示:如果您不确定某个可观察对象是否正在自动取消订阅/完成,请向complete添加一个subscribe(...)回调并检查是否当组件被销毁时会被调用。

答案 4 :(得分:14)

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

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

getHero()会返回http.get()的结果。如果您查看角度2 source codehttp.get()会创建两个事件侦听器:

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

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

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

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

通常情况下,这足以保证手动unsubscribe()电话。但是根据这个WHATWG specXMLHttpRequest()一旦被“完成”就会被垃圾收集,即使附加了事件监听器也是如此。所以我想这就是为什么angular 2官方指南省略unsubscribe()并让GC清理听众。

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

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

如有疑问,请查看该观察结果的实施情况。如果observable在其unsubscribe()中写了一些清理逻辑,这通常是构造函数返回的函数,那么你有充分的理由认真考虑调用unsubscribe()

答案 5 :(得分:6)

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

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

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

  

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

     

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

我希望这会对你有所帮助。

答案 6 :(得分:4)

基于:Using Class inheritance to hook to Angular 2 component lifecycle

另一种通用方法:

&#13;
&#13;
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
  }

}
&#13;
&#13;
&#13;

并使用:

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

答案 7 :(得分:3)

由于seangwright的解决方案(编辑3)似乎非常有用,我还发现将此功能打包到基本组件中很麻烦,并且暗示其他项目团队成员记得在ngOnDestroy上调用super()来激活此功能。 / p>

这个答案提供了一种从超级调用中解脱出来的方法,并使“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');
    }

}

答案 8 :(得分:3)

正式的编辑#3答案(和变体)运作良好,但让我感觉到的是围绕可观察订阅的业务逻辑的“混乱”。

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

  

Warining:实验代码

文件 subscribeAndGuard.ts 用于创建一个新的Observable扩展来包装.subscribe()并在其中包裹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');
  }
}

演示插件是here

附加说明: 重新编辑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;
  }
}

答案 9 :(得分:3)

按照@seangwright的回答,我写了一个抽象类来处理组件中“无限”observables的订阅:

subscribe()

要使用它,只需将其扩展到角度组件中,然后按如下方式调用this.subscribe(someObservable, data => doSomething()); 方法:

super.ngOnDestroy()

它也像往常一样接受错误和完整的回调,一个观察者对象,或者根本不接受回调。如果您还在子组件中实现该方法,请记得调用"NaN"

在这里找到Ben Lesh的另一个参考:RxJS: Don’t Unsubscribe

答案 10 :(得分: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() {}
}

答案 11 :(得分:2)

我尝试了seangwright的解决方案(编辑3)

对于由计时器或间隔创建的Observable不起作用。

然而,我通过另一种方法使其工作:

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

答案 12 :(得分:1)

ngOnDestroy 功能(角度生命周期)的SPA应用程序中

,对于每个 subscribe ,您需要取消订阅。优势=>防止状态变得过重。

例如: 在component1中:

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

在component2中:

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

答案 13 :(得分:1)

对于在发出诸如AsyncSubject之类的结果之后立即完成的可观察对象,例如对于来自HTTP请求的可观察对象,您无需取消订阅。 调用它们unsubscribe()并没有什么坏处,但是如果可观察值是closed,则退订方法will simply not do anything

if (this.closed) {
  return;
}

当您的长期观测对象随时间推移会发出多个值(例如BehaviorSubjectReplaySubject)时,您需要取消订阅以防止内存泄漏。

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

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

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

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会在发出第一个值之后(或在传递函数参数时第一个满足您的谓词的值)直接完成,因此无需取消订阅。

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

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

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

firstsingle看起来非常相似,两个管道都可以采用可选谓词,但是区别很重要,并且在this stackoverflow answer here中进行了很好的总结:

第一

第一项出现时将立即发射。将在此之后完成。

如果可观察的源发出多个事件,则将失败。


注意 我在尝试回答官方文档时尽可能做到准确和完整,但是如果缺少重要内容,请发表评论... < / p>

答案 14 :(得分:1)

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

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方法。

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

答案 15 :(得分:1)

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

  • 始终取消订阅,当订阅流中的新值不再需要或不重要时,在少数情况下会导致触发器数量减少和性能提升。诸如订阅数据/事件不再存在的组件或者需要对新流进行新订阅(刷新等)的情况是取消订阅的一个很好的例子。

答案 16 :(得分:1)

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

  

您需要取消订阅吗?

如下所述   ActivatedRoute:路线信息部分的一站式服务   路由&amp;导航页面,路由器管理它的可观察量   提供和本地化订阅。订阅是   组件被破坏时清理,防止内存   泄漏,因此您无需取消订阅路径paramMap   可观察到的。

下面的例子是一个很好的例子,从Angular创建一个组件并在之后销毁它,看看组件如何实现OnDestroy,如果你需要onInit,你也可以在你的组件中实现它,比如implements OnInit, 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();
  }
}

答案 17 :(得分:0)

对于处理订阅,我使用“取消订阅者”类。

这是退订类。

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

答案 18 :(得分:0)

您可以使用最新的 Subscription 类以不太混乱的代码退订Observable。

我们可以使用normal variable来执行此操作,但是在每个新的订阅中它将使用override the last subscription,因此请避免这种情况,当您处理更多数量的Obseravable和更多类型的Obseravable时,此方法非常有用 BehavoiurSubject Subject

订阅

  

表示可抛弃的资源,例如Observable的执行。订阅具有一种重要的方法,即取消订阅,该方法不带任何参数,而是仅处理订阅所拥有的资源。

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

  • 您可以直接将订阅推送到Subscription Array

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

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

希望这会有所帮助..:)

答案 19 :(得分:0)

SubSink软件包,一种简单,一致的退订解决方案

正如没有人提到的那样,我想推荐Ward Bell创建的Subsink软件包:https://github.com/wardbell/subsink#readme

我一直在一个项目上使用它,因为我们有几个开发人员都在使用它。在每种情况下都有一种一致的工作方式很有帮助。

答案 20 :(得分:0)

订阅实际上仅具有unsubscribe()函数以释放资源或取消可观察的执行。 在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()
    }
}

我们在我们的AppCompoennt中添加了ngOnDestroy,并在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()
    }
}

答案 21 :(得分: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.
  }
}

答案 22 :(得分: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);
    }
}

Live Example

这里发生了什么

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

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

<块引用>

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

答案 23 :(得分:0)

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

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

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

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 = [];
    }
}

组件内部的用法如下:

当你想订阅任何服务时,只需将订阅放到订阅者的 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();
}

答案 24 :(得分:0)

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

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

请找出如何实现的示例

app.component.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.component.html

<h3>AsyncPipe with Promise using NgFor</h3>
<ul>
  <li *ngFor="let book of books$ | async" >
    Id: {{book?.id}}, Name: {{book?.name}}
  </li>
</ul>

答案 25 :(得分:-1)

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

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

  constructor () {}

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

  ngOnDestroy() {
    this.subscriptions.unsubscribe();
  }
}
  1. 在Rxjs中使用takeUntil
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组件中使用)。