什么时候我应该存储订阅实例和调用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();
}

当前回答

对于像AsyncSubject这样直接发送结果的可观察对象,或者来自http请求的可观察对象,你不需要取消订阅。 对这些对象调用unsubscribe()也无妨,但如果可观察对象被关闭,则unsubscribe方法将不会做任何事情:

if (this.closed) {
  return;
}

当你有长期存在的可观察对象,它会随着时间的推移发出多个值(比如一个BehaviorSubject或一个ReplaySubject),你需要取消订阅以防止内存泄漏。

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

如果Observable在发送下一个通知之前完成,则向观察者的错误回调传递一个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将在发出第一个值后直接完成(或者在向函数参数传递满足谓词的第一个值时),因此不需要取消订阅。

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

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

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

第一个和单个看起来非常相似,两个管道都可以接受一个可选的谓词,但区别是重要的,并在这里的stackoverflow回答中很好地总结:

第一个 将在第一个项目出现时立即发出。之后就会完成。 单 如果源可观察对象发出多个事件将失败。


注意,在我的回答中,我尽量准确和完整地参考了官方文件,但如果遗漏了重要的东西,请评论……

其他回答

你可以使用最新的订阅类来取消对Observable的订阅,代码不会那么乱。

我们可以用普通变量来做这个,但它会在每次新的订阅上覆盖上一次订阅,所以要避免这种情况,当你处理更多数量的可观察对象时,这种方法是非常有用的,以及像behaviour Subject和Subject这样的可观察对象类型

订阅

表示一个一次性资源,比如Observable的执行。订阅有一个重要的方法,即unsubscribe,它不接受参数,只处理订阅所持有的资源。

你可以用两种方式来使用它,

you can directly push the subscription to 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()); } using add() of Subscription subscriptions = new Subscription(); this.subscriptions.add(subscribeOne); this.subscriptions.add(subscribeTwo); ngOnDestroy() { this.subscriptions.unsubscribe(); }

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

希望这能有所帮助。:)

视情况而定。如果通过调用someObservable.subscribe(),您开始占用一些资源,当组件的生命周期结束时必须手动释放这些资源,那么您应该调用subscription .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清理侦听器。

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

你的问题的底线是总是调用unsubscribe()来防止内存泄漏,除非你确定可观察对象的执行不会创建全局变量、添加事件侦听器、设置计时器或做任何其他导致内存泄漏的事情。

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

官方的Edit #3答案(及其变体)工作得很好,但让我困惑的是围绕可观察订阅的业务逻辑的“混乱”。

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

警告:实验代码

文件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');
  }
}

演示活塞在这里

附加说明: Re Edit 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;
  }
}

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

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

我们添加了ngOnDestroy到我们的appcomponent,并在这个上调用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()
    }
}