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

Angular/RxJs When should I unsubscribe from `Subscription`

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

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

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

1
2
3
4
5
6
getHeroes() {
  this.heroService.getHeroes()
                   .subscribe(
                     heroes => this.heroes = heroes,
                     error =>  this.errorMessage = error);
}

同时路线& 导航指南说:

Eventually, we'll navigate somewhere else. The router will remove this component from the DOM and destroy it. We need to clean up after ourselves before that happens. Specifically, we must unsubscribe before Angular destroys the component. Failure to do so could create a memory leak.

We unsubscribe from our Observable in the ngOnDestroy method.

1
2
3
4
5
6
7
8
9
10
11
12
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 - 其他资源(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的方式。

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

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

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

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

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

  • 我在对Nicholas博客的评论中提到过度使用takeUntil()可能表明你的组件试图做太多,并且应该考虑将现有组件分离为Feature和Presentational组件。然后,您可以将| async从功能组件中的Observable导入到演示组件的Input中,这意味着在任何地方都不需要订阅。在这里阅读更多关于这种方法

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

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

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

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

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

    例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    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运算符添加为最后一个运算符非常重要,以防止运算符链中的中间可观察对象泄漏。

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

    来源5

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

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

    ---编辑1

    来源4

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

    ---原始答案

    TLDR:

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

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

    如果手动调用subscribe(不使用异步管道),则unsubscribe来自无限Observables

    不要担心有限的,RxJs会照顾它们。

    来源1

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

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

    if its a single-value-sequence (like an http request)
    the manual cleanup is unnecessary (assuming you subscribe in the controller manually)

    Ok.

    i should say"if its a sequence that completes" (of which single value sequences, a la http, are one)

    Ok.

    if its an infinite sequence, you should unsubscribe which the async pipe does for you

    Ok.

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

    来源2

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

    In most cases we will not need to explicitly call the unsubscribe method unless we want to cancel early or our Observable has a longer lifespan than our subscription. The default behavior of Observable operators is to dispose of the subscription as soon as .complete() or .error() messages are published. Keep in mind that RxJS was designed to be used in a"fire and forget" fashion most of the time.

    Ok.

    什么时候短语our Observable has a longer lifespan than our subscription适用?

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

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

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

    来源3

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

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

    好。


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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    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();
        }
    }

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

    所以这是代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    @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;
        }
    }


    Subscription类有一个有趣的特性:

    Represents a disposable resource, such as the execution of an Observable. A Subscription has one important method, unsubscribe, that takes no argument and just disposes the resource held by the subscription.
    Additionally, subscriptions may be grouped together through the add() method, which will attach a child Subscription to the current Subscription. When a Subscription is unsubscribed, all its children (and its grandchildren) will be unsubscribed as well.

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    @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();
      }
    }


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

    来自Routing & Navigation的引用

    When subscribing to an observable in a component, you almost always arrange to unsubscribe when the component is destroyed.

    Ok.

    There are a few exceptional observables where this is not necessary. The ActivatedRoute observables are among the exceptions.

    Ok.

    The ActivatedRoute and its observables are insulated from the Router itself. The Router destroys a routed component when it is no longer needed and the injected ActivatedRoute dies with it.

    Ok.

    Feel free to unsubscribe anyway. It is harmless and never a bad practice.

    Ok.

    并在回应以下链接:

  • (1)我应该取消订阅Angular 2 Http Observables吗?
  • (2)是否有必要取消订阅Http方法创建的可观察量?
  • (3)RxJS:不要取消订阅
  • (4)取消订阅Angular中Observables的最简单方法
  • (5)RxJS取消订阅的文档
  • (6)取消订阅服务是没有意义的,因为没有内存泄漏的可能性
  • (7)我们是否需要取消订阅完成/错误输出的可观察性?
  • (8)关于http可观察的评论
  • 我收集了一些关于Angular组件中可观察的取消订阅的最佳实践,以便与您分享:

  • http observable unsubscription是有条件的,我们应该考虑在逐个销毁组件之后运行'subscribe callback'的效果。我们知道angular取消订阅并清除http observable本身(1),(2)。虽然从资源的角度来看这是真的,但它只讲述了一半的故事。假设我们正在谈论直接从组件中调用http,并且http响应花费的时间比需要的时间长,因此用户关闭了组件。即使组件已关闭并销毁,仍将调用subscribe()处理程序。这可能会产生不必要的副作用,在更糟糕的情况下会使应用程序状态中断。如果回调中的代码试图调用刚被处理掉的东西,它也会导致异常。然而,偶尔他们是偶然的。比如,假设您正在创建一个电子邮件客户端,并在电子邮件发送完成后触发声音 - 即使组件已关闭,您仍然希望发生这种情况(8)。
  • 无需取消订阅完成或错误的可观察对象。但是,这样做没有害处(7)。
  • 尽可能使用AsyncPipe,因为它会自动取消订阅组件销毁时的observable。
  • 如果它们在嵌套(在组件选择器中添加了内部tpl)或动态组件内订阅,则取消订阅ActivatedRoute可观察项,如route.params,因为只要父/主机组件存在,它们可能会多次订阅。无需在Routing & Navigation docs上面的引用中提到的其他方案中取消订阅它们。
  • 取消订阅通过Angular服务公开的组件之间共享的全局可观察对象,例如,只要组件初始化,它们可能会多次订阅。
  • 无需取消订阅应用程序范围服务的内部可观察量,因为此服务永远不会被销毁,除非您的整个应用程序被销毁,没有真正的理由取消订阅并且没有内存泄漏的可能性。 (6)。
    注意:关于作用域服务,即组件提供程序,它们在销毁组件时被销毁。在这种情况下,如果我们订阅了这个提供程序中的任何observable,我们应该考虑使用OnDestroy生命周期钩子来取消订阅,根据文档将在服务被销毁时调用它。
  • 使用抽象技术可以避免因取消订阅而导致的任何代码混乱。您可以使用takeUntil(3)管理您的订阅,或者您可以使用(4)中提到的npm包。在Angular中取消订阅Observables的最简单方法。
  • 始终取消订阅FormGroupform.statusChangesFormGroup可观察对象
  • 始终取消订阅Renderer2服务的可观察量,例如renderer2.listen
  • 取消订阅其他可观察的其他内容作为内存泄漏保护步骤,直到Angular Docs明确告诉我们哪些observable不需要取消订阅(检查问题:(5)RxJS Unsubscribing(Open)文档)。
  • 额外:始终使用Angular方法绑定像HostListener这样的事件,因为如果需要,可以很好地删除事件侦听器,并防止因事件绑定而导致的任何潜在内存泄漏。
  • 一个不错的最终提示:如果您不知道是否正在自动取消订阅/完成一个observable,请将complete回调添加到subscribe(...)并检查它是否在组件被销毁时被调用。

    好。


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

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

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

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

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

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

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

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

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

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

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


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

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

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

    Notice that we capture the subscription and unsubscribe when the AstronautComponent is destroyed. This is a memory-leak guard step. There is no actual risk in this app because the lifetime of a AstronautComponent is the same as the lifetime of the app itself. That would not always be true in a more complex application.

    We do not add this guard to the MissionControlComponent because, as the parent, it controls the lifetime of the MissionService.

    我希望这可以帮助你。


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

    另一种通用方法:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    export abstract class UnsubscribeOnDestroy implements OnDestroy {
      protected d$: Subject;

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

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

      public ngOnDestroy() {
        // no-op
      }

    }

    并使用:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    @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));
        }
    }


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

    这个答案提供了一种从超级调用中解脱出来的方法,并使"co??mponentDestroyed $"成为基本组件的核心。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    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() {}
    }

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    @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');
        }

    }

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

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

    Warining: experimental code

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    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不知道调用包装版本。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    import { Component, OnInit, OnDestroy } from '@angular/core';
    import { Observable } from 'rxjs/Observable';
    import 'rxjs/Rx';
    import './subscribeAndGuard';

    @Component({
      selector: 'app-subscribing',
      template: 'Subscribing component is active',
    })
    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');
      }
    }

    这里有一个演示插件

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    @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;
      }
    }

    在@seangwright的回答之后,我编写了一个抽象类来处理组件中的"无限"observables订阅:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    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 = new Subject();

      constructor() {}

      subscribe(observable: Observable): Subscription;

      subscribe(
        observable: Observable,
        observer: PartialObserver
      ): Subscription;

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

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

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

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

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

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

    在这里找到Ben Lesh的另外一个参考:RxJS:不要取消订阅。


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

    这不适用于由计时器或间隔创建的Observable。

    但是,我通过使用另一种方法使其工作:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    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();
       }
     }
    }


    我喜欢最后两个答案,但如果子类在ngOnDestroy中引用了"this",我遇到了一个问题。

    我修改它是这样,看起来它解决了这个问题。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    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() {}
    }


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

    Do you need to unsubscribe? As described in the
    ActivatedRoute: the one-stop-shop for route information section of the
    Routing & Navigation page, the Router manages the observables it
    provides and localizes the subscriptions. The subscriptions are
    cleaned up when the component is destroyed, protecting against memory
    leaks, so you don't need to unsubscribe from the route paramMap
    Observable.

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    import { Component, Input, OnDestroy } from '@angular/core';  
    import { MissionService } from './mission.service';
    import { Subscription }   from 'rxjs/Subscription';

    @Component({
      selector: 'my-astronaut',
      template: `
        <p>

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


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

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

    如果需要取消订阅,可以使用以下操作符来进行可观察的管道方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    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 || new Subject();

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

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

    它可以像这样使用:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    import { Component, OnDestroy, OnInit } from '@angular/core';
    import { Observable } from 'rxjs';

    @Component({ template: '' })
    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方法。

    重要提示:运算符应该是可观察管道中的最后一个。


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

    例如:
    在component1中:

    1
    2
    3
    4
    5
    6
    7
    import {UserService} from './user.service';

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

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

    在服务中:

    1
    2
    3
    import {BehaviorSubject} from 'rxjs/BehaviorSubject';

    public onUserChange: BehaviorSubject = new BehaviorSubject({});

    在component2中:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    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();
    }


    对于处理订阅,我使用"Unsubscriber"类。

    这是Unsubscriber类。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    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();
      }
    }

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

    例:

    1
    2
    3
    4
    5
    6
    7
    class SampleComponent extends Unsubscriber {
        constructor () {
            super();
        }

        this.addSubscription(subscription);
    }