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

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

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

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

同时,《航路指南》指出:

最终,我们会航行到别的地方。路由器将从DOM中移除这个组件并销毁它。在那之前,我们得把自己弄干净。具体来说,我们必须在Angular销毁该组件之前取消订阅。如果不这样做,可能会产生内存泄漏。 我们在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();
}

当前回答

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

要使用它,只需在你的angular组件中扩展它,并调用subscribe()方法,如下所示:

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

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

在这里可以找到Ben Lesh的另一篇参考文章:RxJS: Don’t Unsubscribe。

其他回答

在我的情况下,我使用了@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.
  }
}

Angular 2官方文档提供了一个关于何时退订以及何时可以安全忽略的解释。看看这个链接:

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

寻找标题为“父母和孩子通过服务通信”的段落,然后是蓝色方框:

注意,当AstronautComponent被销毁时,我们捕获了订阅并取消了订阅。这是一个内存泄漏保护步骤。在这个应用程序中没有实际的风险,因为AstronautComponent的生命周期与应用程序本身的生命周期相同。在更复杂的应用程序中,这并不总是正确的。 我们没有将这个守卫添加到MissionControlComponent中,因为作为父组件,它控制着MissionService的生命周期。

我希望这对你有所帮助。

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

这对timer或interval创建的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();
   }
 }
}

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

你需要取消订阅吗?如在 ActivatedRoute:一站式的路线信息部分 路由和导航页面,路由器管理它的观察对象 提供并本地化订阅。订阅是 当组件被破坏时进行清理,以防止内存 泄漏,所以你不需要从路由paramMap取消订阅 可观察到的。

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

订阅类有一个有趣的特性:

表示一个一次性资源,比如Observable的执行。订阅有一个重要的方法,即unsubscribe,它不接受参数,只处理订阅所持有的资源。 此外,可以通过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();
  }
}