web-dev-qa-db-ja.com

Angular/RxJsいつSubscriptionから購読を中止すべきですか

NgOnDestroyのライフサイクル中にいつSubscriptionインスタンスを保存してunsubscribe()を呼び出すべきですか?

すべての購読を保存すると、コンポーネントコードに多くの混乱が生じます。

HTTPクライアントガイド このような購読を無視する:

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

同じ時間で ルートとナビゲーションガイド はそれを言う:

最終的には、他の場所に移動します。ルータは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();
}
575
Sergey Tihon

---編集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に言及し、シャイ・レズニックはAngular handles some of the subscriptions like http and routingに言及しています。

これに対して、BenはObservablesがAngularコンポーネントライフサイクルイベントにフックできるようにするための議論が今あると述べ、WardはObservablesを完了するタイミングを知る方法として、コンポーネントがサブスクライブできるライフサイクルイベントのObservableを提案していますコンポーネントの内部状態として維持されます。

とはいえ、今はほとんどのソリューションが必要なので、他のリソースもいくつかあります。

  1. RxJsコアチームメンバーのNicholas JamiesonによるtakeUntil()パターンの推奨事項と、それを実施するためのtslintルール。 https://blog.angularindepth.com/rxjs-avoiding-takeuntil-leaks-fb5182d047ef

  2. コンポーネントインスタンス(this)をパラメーターとして受け取り、ngOnDestroy中に自動的にサブスクライブを解除するObservableオペレーターを公開する軽量npmパッケージ。 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と- プレゼンテーションコンポーネントを検討する必要があります。 | async ObservableをFeatureコンポーネントからPresentationalコンポーネントのInputに入れることができます。つまり、サブスクリプションはどこにも必要ありません。このアプローチの詳細を読む here

---編集3-「公式」ソリューション(2017/04/09)

私はNGConfでこの質問についてWard Bellと話をしました(彼が正しいと言ったこの答えさえ見せました)が、彼はAngularのドキュメントチームにこの問題に対する解決策があります承認に取り組んでいます)。彼はまた、今後の公式勧告でSO回答を更新できると言った。

今後使用するソリューションは、クラスコード内でObservablesをprivate ngUnsubscribe = new Subject();呼び出しするすべてのコンポーネントに.subscribe()フィールドを追加することです。

次に、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チュートリアルのルーティングの章では、次のように述べています。「ルーターは、提供するオブザーバブルを管理し、サブスクリプションをローカライズします。サブスクリプションは、コンポーネントが破壊されるとクリーンアップされ、メモリリークから保護されるため、 「ルートパラメータObservableの登録を解除する必要はありません。」 - Mark Rajcok

以下は、ルーター監視に関するAngularドキュメントのGithub問題に関する discussion です。WardBellは、このすべての説明が進行中であると述べています。

---編集1

ソース4

この中で NgEuropeからのビデオ Rob Wormaldは、ルーターオブザーバブルから退会する必要がないとも言っています。彼はまた、この中のhttpサービスとActivatedRoute.paramsに言及しています 2016年11月のビデオ

---元の回答

TLDR:

この質問には、(2)種類のObservables-finite valueとinfinite valueがあります。

httpObservablesfinite(1)値とDOMのようなものevent listenerObservablesinfinite値を生成します。

手動でsubscribeを呼び出す(非同期パイプを使用しない)場合は、infiniteunsubscribeからObservablesを呼び出します。

finite onesについて心配する必要はありません。RxJsがそれらを処理します。

ソース1

Angular's GitterのRob Wormaldからの回答を追跡しました here

彼は次のように述べています(明確にするために再編成し、強調するのは私のものです)

単一値シーケンス(httpリクエストのように)の場合手動でのクリーンアップは不要(コントローラーで手動でサブスクライブすると仮定)

完了するシーケンス(そのうち、httpのような単一値シーケンスが1つである場合)」と言う必要があります。

その無限シーケンスの場合サブスクライブ解除する必要がある非同期パイプがあなたのために行う

また、彼はこれに言及しています youtube video Observables they clean up after themselves...は、complete(Promisesのように、常に1つの値を生成して終了するため、常に完了するObservablesのコンテキストで) xhrイベントリスナーを確実にクリーンアップするためにPromisesの登録を解除することを心配していませんか?).

ソース2

また、 Angular 2へのRangleガイド

ほとんどの場合、早期にキャンセルしたい場合や、Observableのサブスクリプションよりも長いライフスパンがある場合を除き、明示的にunsubscribeメソッドを呼び出す必要はありません。 Observableオペレーターのデフォルトの動作は、.complete()または.error()メッセージが公開されるとすぐにサブスクリプションを破棄することです。 RxJSは、ほとんどの場合「火と忘れ」の方法で使用されるように設計されていることに注意してください。

フレーズour Observable has a longer lifespan than our subscriptionはいつ適用されますか?

Observableが完了する前に(または「長くない」前に)破棄されるコンポーネント内でサブスクリプションが作成されるときに適用されます。

httpリクエストまたは10個の値を発行するObservableをサブスクライブし、httpリクエストが返される前、または10個の値が発行される前にコンポーネントが破棄された場合、これを意味と読みます。

要求が返されるか、10番目の値が最終的に発行されると、Observableが完了し、すべてのリソースがクリーンアップされます。

ソース

同じRangleガイドの この例 を見ると、Subscriptionからroute.paramsunsubscribe()が必要であることがわかります。なぜなら、paramsがいつ変更を停止するかわからないからです。 (新しい値を放出する)。

コンポーネントは、離れてナビゲートすることで破棄できます。その場合、ルートパラメータは変更される可能性が高く(アプリが終了するまで技術的に変更される可能性があります)、completionがないため、サブスクリプションで割り当てられたリソースは依然として割り当てられます。

898
seangwright

多数のサブスクリプションを用意して、手動でサブスクリプションを解除する必要はありません。 Subject および takeUntil を使用して、ボスのようなサブスクリプションを処理します。

import { Subject } from "rxjs"
import { takeUntil } from "rxjs/operators"

@Component({
  moduleId: __moduleName,
  selector: "my-view",
  templateUrl: "../views/view-route.view.html"
})
export class ViewRouteComponent implements OnInit, OnDestroy {
  componentDestroyed$: Subject<boolean> = new Subject()

  constructor(private titleService: TitleService) {}

  ngOnInit() {
    this.titleService.emitter1$
      .pipe(takeUntil(this.componentDestroyed$))
      .subscribe((data: any) => { /* ... do something 1 */ })

    this.titleService.emitter2$
      .pipe(takeUntil(this.componentDestroyed$))
      .subscribe((data: any) => { /* ... do something 2 */ })

    //...

    this.titleService.emitterN$
      .pipe(takeUntil(this.componentDestroyed$))
      .subscribe((data: any) => { /* ... do something N */ })
  }

  ngOnDestroy() {
    this.componentDestroyed$.next(true)
    this.componentDestroyed$.complete()
  }
}

代替アプローチ、提案された コメントの@acumartiniによる 、-の代わりに takeWhile を使用 takeUntil 。それを好むかもしれませんが、この方法では、コンポーネントのngDestroyでObservableの実行がキャンセルされないことに注意してください(たとえば、時間のかかる計算を行うときやサーバーからのデータを待つとき)。 takeUntil に基づくメソッドには、この欠点はなく、リクエストが即座にキャンセルされます。 コメントの詳細な説明については@AlexCheに感謝します

コードは次のとおりです。

@Component({
  moduleId: __moduleName,
  selector: "my-view",
  templateUrl: "../views/view-route.view.html"
})
export class ViewRouteComponent implements OnInit, OnDestroy {
  alive: boolean = true

  constructor(private titleService: TitleService) {}

  ngOnInit() {
    this.titleService.emitter1$
      .pipe(takeWhile(() => this.alive))
      .subscribe((data: any) => { /* ... do something 1 */ })

    this.titleService.emitter2$
      .pipe(takeWhile(() => this.alive))
      .subscribe((data: any) => { /* ... do something 2 */ })

    // ...

    this.titleService.emitterN$
      .pipe(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
  }
}
75
metamaker

Subscriptionクラスには興味深い機能があります。

Observableの実行など、使い捨てのリソースを表します。サブスクリプションには、引数を取らず、サブスクリプションが保持しているリソースを破棄するだけの重要なメソッド、unsubscribeがあります。
さらに、サブスクリプションは、現在のサブスクリプションに子サブスクリプションを添付するadd()メソッドを介してグループ化することができます。購読が購読解除されると、そのすべての子(およびその孫)も購読解除されます。

すべての購読をグループ化する集約Subscriptionオブジェクトを作成できます。これを行うには、空の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();
  }
}
53
Steven Liekens

場合によります。 someObservable.subscribe()を呼び出して、コンポーネントのライフサイクルが終了したときに手動で解放しなければならないリソースを保留し始めた場合は、メモリリークを防ぐためにtheSubscription.unsubscribe()を呼び出す必要があります。

例を詳しく見てみましょう。

getHero()http.get()の結果を返します。角度2 のソースコード を見ると、http.get()は2つのイベントリスナーを作成します。

_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がリスナーをクリーンアップするのはそのためです。

あなたの2番目の例に関しては、それはparamsの実装に依存します。今日の時点で、正式な公式ガイドにはもはやparamsからの退会が示されていません。もう一度 src を調べたところ、paramsは単なる BehaviorSubject であることがわかりました。イベントリスナーやタイマーは使用されておらず、グローバル変数も作成されていないので、unsubscribe()を省略しても安全なはずです。

あなたの質問の一番下の行は、オブザーバブルの実行がグローバル変数を作成したり、イベントリスナを追加したり、タイマーを設定したり、あるいはメモリをもたらすその他のことをしないことが確実でない限り、常にメモリリークを防ぐためにunsubscribe()を呼ぶことです漏れます。

疑問がある場合は、そのオブザーバブルの実装を調べてください。オブザーバブルがクリーンアップロジックをそのunsubscribe()(通常はコンストラクタによって返される関数)に書き込んでいる場合は、unsubscribe()の呼び出しを真剣に検討する十分な理由があります。

14
evenstar

Angular 2の公式ドキュメントには、いつ購読を中止するか、またいつ無視しても問題ないかについての説明が記載されています。このリンクを見てください。

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

という見出しの付いた段落を探します。親と子はサービス を介して通信し、次に青いボックスを介して通信します。

AstronautComponentが破棄されると、購読が開始され、購読が解除されます。これはメモリリークガードのステップです。 AstronautComponentの寿命はアプリ自体の寿命と同じであるため、このアプリには実際のリスクはありません。より複雑なアプリケーションでは、必ずしもそうとは限りません。

親としてMissionServiceの有効期間を制御するため、このガードをMissionControlComponentに追加しません。

これがお役に立てば幸いです。

6
Cerny

に基づく: クラス継承を使用してAngular 2コンポーネントのライフサイクルにフック

もう一つの一般的なアプローチ:

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

Seangwrightの解決策(編集3)は非常に有用であるように思われるので、この機能を基本コンポーネントにまとめるのは面倒であり、他のプロジェクトチームメイトにngOnDestroyでsuper()を呼び出すのを忘れないでください。

この答えは、スーパーコールから解放され、 "componentDestroyed $"を基本コンポーネントの中核にする方法を提供します。

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
Val

公式のEdit#3の答え(およびそのバリエーション)はうまく機能しますが、私を惹きつけるのは、観察可能なサブスクリプションに関するビジネスロジックの「曖昧さ」です。

これはラッパーを使った別のアプローチです。

警告: 実験コード

ファイルsubscribeAndGuard.tsは、.subscribe()をラップし、その中にngOnDestroy()をラップするための新しいObservable拡張機能を作成するために使用されます。
使用法は、コンポーネントを参照する追加の最初のパラメーターを除いて、.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;
  }
}

これは、2つのサブスクリプションを持つコンポーネントです。1つはラッパー付き、もう1つはサブスクライブなしです。唯一の注意点は 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');
  }
}

デモプランカーは ここ

追加の注意事項: 再編集3 - 'オフィシャル'ソリューション。これは、購読の前にtakeUntil()の代わりにtakeWhile()を使用し、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;
  }
}
3
Richard Matsen

@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:購読中止

3
Mau Muñoz

私は最後の2つの答えが好きですが、サブクラスがngOnDestroy"this"を参照している場合に問題が発生しました。

私はそれをこれに修正しました、そしてそれはそれがその問題を解決したようです。

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

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
Jeff Tham

通常、コンポーネントが破棄されたときには購読を中止する必要がありますが、Angularは今後ますます処理するようになります。例えばAngular4の新しいマイナーバージョンでは、購読中止のためのセクションがあります。

退会する必要がありますか?

Routing&NavigationページのActivatedRoute:経路情報のワンストップショップセクションで説明されているように、ルーターは提供するオブザーバブルを管理し、サブスクリプションをローカライズします。コンポーネントが破壊されたときにサブスクリプションはクリーンアップされ、メモリリークから保護されます。そのため、ルート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
Alireza

購読解除が必要な場合は、観察可能なパイプメソッドに対して次の演算子を使用できます

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
Oleg Polezky

上記の状況に加えて、もう1つ短いことがあります。

  • 購読しているストリームの新しい値が不要になったり問題にならなくなった場合は、常に購読を中止します。トリガーの数が少なくなり、パフォーマンスが向上することがあります。購読しているデータ/イベントがもう存在しない、またはまったく新しいストリームへの新しい購読が必要なコンポーネント(更新など)などのケースは、購読解除の良い例です。
1
kg11

sPAアプリケーションで ngOnDestroy function(angle lifeCycle) subscribe それぞれに unsubscribe itが必要です。状態が重くなりすぎないようにする.

たとえば、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();
}
1

購読を処理するために、 "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);
}
0
Pratiyush