Angular 2 - 订阅 FormControl 的 valueChanges 是不是需要取消订阅?

Posted

技术标签:

【中文标题】Angular 2 - 订阅 FormControl 的 valueChanges 是不是需要取消订阅?【英文标题】:Angular 2 - Does subscribing to FormControl's valueChanges need an unsubscribe?Angular 2 - 订阅 FormControl 的 valueChanges 是否需要取消订阅? 【发布时间】:2017-05-12 20:33:26 【问题描述】:

下面是我所问问题的一个简单示例:

class AppComponent 

  someInput: FormControl = new FormControl('');
  private subscription: Subscription;

  constructor()

    this.subscription = this.someInput.valueChanges
        .subscribe(() => console.log("testing"));
  

我有两个问题:

1) 我必须最终取消订阅吗? 2) 最重要的是,无论#1 的答案是什么,为什么是这个答案?

任何见解将不胜感激!

谢谢!

【问题讨论】:

【参考方案1】:

2020 年 10 月更新

这个问题获得了大量的 SEO 流量,虽然我的回答在技术上仍然是正确的,但我强烈建议您 read this。


    是的,你知道。请注意,您不需要 unsubscribe from http calls,因为它们会自动完成。

    您必须取消订阅以防止内存泄漏并避免应用程序中出现意外的副作用。

例如,当一个组件被销毁时,如果您不取消订阅,订阅将保留。下次用户导航回该页面并再次加载组件时,它将创建另一个订阅 valueChanges - 现在您有 2 个活动订阅同时处理数据,这将导致您的应用出现意外行为。

有一种相对简单的方法来设置自动退订。

RxJS 的方式是使用takeUntil - 它基本上可以让你设置一个订阅者继续收听直到你告诉它不要这样做 :)

首先我们需要在我们的组件中销毁主题:

  private destroy$ = new Subject();

然后告诉它在我们的组件被销毁时发出一个值:

  ngOnDestroy()
    this.destroy$.next();
    this.destroy$.complete(); 
  

现在设置您的订阅以使用它:

this.someInput.valueChanges
  .debounceTime(1000)
  .distinctUntilChanged()
  .takeUntil(this.destroy$)
  .subscribe (newValue => 

所有像这样的订阅设置将在收到销毁通知时自动取消订阅。

这尤其有用,例如,如果您有一个动态添加控件的 ngFor,并且添加的每个控件都有一个 valueChanges 订阅(例如,控件数组)。在这种情况下,您不需要遍历 ngOnDestroy 中的数组并逐个取消订阅。只需使用 takeUntil :)

我建议阅读 Ben Lesh's article(Google 的 RxJS 首席工程师),以全面了解 takeUntil,包括优缺点。

【讨论】:

只是想知道,当组件被销毁并且有资格进行 gc 时,没有其他引用 someInput 对象以及其'valueChanges 可观察实例和订阅函数,所以它们应该是合格的对于 gc 也是如此。那么为什么会有内存泄漏呢? rxjs 是否在Observable 实例外部 中保留对订阅函数的引用?这听起来不太可能,但我不熟悉 rxjs 的内部工作原理。我在使用 interval 或类似名称时看到了问题,但在这种情况下没有。 @nej_simon 这确实是一个非常好的问题。我的理解是 rxjs 可观察对象和订阅“存在于 Angular 生命周期之外”。这就是问题所在。这个问题的更多信息:***.com/questions/38008334/… 如果我在 valueChanges 的订阅方法中创建一个 console.log,它不会在每次我的组件被销毁时产生另一个控制台日志,我猜如果它一直挂在内存中会不会? @XRaycat 我认为你在组件被销毁后看不到控制台日志,因为值没有改变,所以事件不会触发。 @rmcshary 那么,如果值没有改变(或者我们可以假设它不会改变),它是如何产生内存泄漏的?【参考方案2】:
    是的。

    无论订阅什么,您都需要取消订阅以防止内存泄漏。有几种方法可以取消订阅。 一种。 “N 次后自动退订” - 添加.take(numberOfTimes) 将在您指定的 N 次后退订。例如:

    this.subscription = this.someInput.valueChanges
        .take(2) // unsubscribe after 2 times
        .subscribe(() => console.log("testing"));
    

    b.在组件生命周期内手动取消订阅,通常是在ngOnDestroy()

【讨论】:

IMO 最好的方法是使用takeUntil 视情况而定。如果知道次数,则 take(n),如果知道该动作在某个场景或动作之前结束,则 takeUnitl() 如果你使用 take(n) 或 takeUntil(),如果用户在 Observable 有机会自动响应和取消订阅之前离开视图会发生什么。在销毁中使用取消订阅不是更安全吗? @Thibs,可能有点晚了,但要回答你的问题,实际上是一样的。当用户离开视图时,Angular 生命周期将调用 ngOnDestroy,因此 this.destroy$.next(true) 将被调用,从而取消订阅 observable。 我这样做的方法是添加对数组的订阅。所以: this.subscriptions.push( this.formfield.valueChanges.subscribe( value => console.log('value: ', value)) ... 然后在 onDestroy 函数中你只需要: this.subscriptions.map( sub => sub.unSubscribe())【参考方案3】:

我强烈推荐 rxjs 扩展 https://github.com/ngneat/until-destroy 。它将帮助您编写更少的代码。

@UntilDestroy()
@Component()
export class InboxComponent 
  ngOnInit() 
    this.someInput.valueChanges
      .pipe(untilDestroyed(this))
      .subscribe();
  


【讨论】:

以上是关于Angular 2 - 订阅 FormControl 的 valueChanges 是不是需要取消订阅?的主要内容,如果未能解决你的问题,请参考以下文章

Angular 2订阅订阅另一个异步功能的服务

Angular 2:找不到名称“订阅”

Angular 2 方法没有订阅 observable

Angular 2:许多异步管道与一个订阅

具有多个订阅者的 Angular 2 Observable

Angular 2:可观察订阅未正确读取数据[重复]