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

问题描述 投票:578回答:17

我应该何时存储Subscription实例并在NgOnDestroy生命周期中调用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();
}
angular rxjs observable subscription angular-component-life-cycle
17个回答
877
投票

---编辑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,其作用类似于异步管道,但在模板中创建了一个嵌入式视图,因此您可以在整个模板中引用“展开”值。 https://netbasal.com/diy-subscription-handling-directive-in-angular-c8f6e762697f

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

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

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

我们今后应该使用的解决方案是在所有在类代码中使用private ngUnsubscribe = new Subject();调用.subscribe()s的组件中添加一个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运算符添加为最后一个运算符非常重要,以防止运算符链中的中间可观察量出现泄漏。

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

来源5

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

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

---编辑1

来源4

在这个video from NgEurope Rob Wormald还说你不需要取消订阅Router Observables。他还在这个http中提到了ActivatedRoute.params服务和video from November 2016

---原始答案

TLDR:

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

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

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

不要担心有限的,RxJs将照顾他们。

来源1

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

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

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

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

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

他还在这个关于Observables的youtube video中提到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


3
投票

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

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

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

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

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

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


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

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

1
投票

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

你需要取消订阅吗?

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

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

1
投票

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

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

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

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


1
投票

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

例如:在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();
}

0
投票

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

这是Unsubscriber类。

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

72
投票

您不需要手动拥有大量订阅和取消订阅。使用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;
    }
}

59
投票

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

26
投票

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

引自Routing & Navigation

订阅组件中的observable时,您几乎总是安排在组件被销毁时取消订阅。

有一些特殊的可观测量,这是没有必要的。 ActivatedRoute可观察量是例外。

ActivatedRoute及其可观察对象与路由器本身绝缘。当不再需要路由器时,路由器会销毁路由组件,并且注入的ActivatedRoute会随之消失。

无论如何,请随意取消订阅。它是无害的,从来都不是一种糟糕的做法。

并在回应以下链接:

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

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

一个不错的最后提示:如果您不知道是否自动取消订阅/完成了一个observable,请将complete回调添加到subscribe(...)并检查它是否在组件被销毁时被调用。


15
投票

这取决于。如果通过调用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()


6
投票

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

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

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

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

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

我希望这可以帮助你。


5
投票

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

另一种通用方法:

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
  }

}

并使用:

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

3
投票

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

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

}

3
投票

官方编辑#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');
  }
}

一个演示的plunker是here

另外一个注释:Re Edit 3 - 'Official'解决方案,这可以通过在订阅之前使用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;
  }
}
© www.soinside.com 2019 - 2024. All rights reserved.