Angular / RxJs `Subscription`からの退会のタイミング


720

NgOnDestroyライフサイクル中にSubscriptionインスタンスを保存して呼び出す必要があるのunsubscribe()はいつですか。

すべてのサブスクリプションを保存すると、コンポーネントコードに多くの混乱が生じます。

HTTPクライアントガイドは、次のようなサブスクリプションを無視します。

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

20
Subscriptions http-requestsonNext1回だけ呼び出してからを呼び出すため、無視できると思いますonCompleteRouter代わりに呼び出し、onNext繰り返し呼び出すことがないかもしれませんonComplete(いないことを確認...そのことについて)。同じことがObservablesからEventsにも当てはまります。だから私はそれらがそうあるべきだと思いますunsubscribed
Springrbua

1
@ gt6707aストリームは、その完了の監視とは無関係に完了します(または完了しません)。サブスクリプション関数に提供されるコールバック(オブザーバー)は、リソースが割り当てられているかどうかを判断しません。subscribe上流にリソースを割り当てる可能性があるのは、それ自体への呼び出しです。
seangwright 2016

回答:


981

---編集4-追加リソース(2018/09/01)

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

これに対してベンは、ObservableがAngularコンポーネントのライフサイクルイベントにフックできるようにするためのディスカッションがあることを述べ、Wardは、コンポーネントの内部状態として維持されるObservableをいつ完了するかを知る方法として、コンポーネントがサブスクライブできるライフサイクルイベントのObservableを提案しています。

そうは言っても、私たちは今ほとんど解決策を必要としているので、ここにいくつかの他のリソースがあります。

  1. takeUntil()RxJのコアチームメンバーであるニコラスジェイミソンからのパターンの推奨と、パターンの実施を支援するtslintルール。https://ncjamieson.com/avoiding-takeuntil-leaks/

  2. コンポーネントインスタンス(this)をパラメーターとして取り、中に自動的にサブスクライブ解除するObservableオペレーターを公開する軽量npmパッケージngOnDestroyhttps://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()で、コンポーネントの過剰使用はコンポーネントがやりすぎていることを示している可能性があり、既存のコンポーネントを機能コンポーネントとプレゼンテーションコンポーネントに分離することを検討する必要があることを述べています。次に| async、ObservableをFeatureコンポーネントからInputPresentationalコンポーネントのに追加できます。つまり、サブスクリプションはどこにも必要ありません。このアプローチの詳細については、こちらをご覧ください

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

私はNGConfでこの質問についてウォードベルと話しました(彼が正しいと言ったこの答えを彼にも示しました)と彼は私にAngularのドキュメントチームが公開されていないこの質問の解決策を持っていると言いました(承認を得るために取り組んでいますが) )。彼はまた、私がSOの回答を次の公式勧告で更新できると私に言った。

今後使用する必要があるソリューションは、クラスコード内でへの呼び出しをprivate ngUnsubscribe = new Subject();持つすべてのコンポーネントにフィールドを追加することです。.subscribe()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チュートリアルのルーティングの章では、次のように述べられています。ルートパラメータObservable。」- マーク・ライコック

これはルーターオブザーバブルに関するAngularドキュメントのGithubの問題に関するディスカッションであり、Ward Bellはこれらすべての明確化が作業中であると述べています。

---編集1

ソース4

NgEuropeからのこのビデオでは、 Rob Wormaldがルーターオブザーバブルの登録を解除する必要がないことも述べています。彼はまた、httpサービスとActivatedRoute.paramsこのビデオで2016年11月から言及しています。

---元の回答

TLDR:

この質問には(2)種類ありますObservables-有限値と無限値があります。

http Observables 作物 有限(1)の値とDOMのようなものをevent listener Observables生み出す無限の値を。

手動で呼び出す場合 subscribe(非同期パイプを使用せずに)はunsubscribeinfinite からObservables

有限のものについて心配しないでくださいRxJs、それらの世話をします。

ソース1

私は角度のギッターにロブWormaldから答えを突き止め、ここ

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

その単一値シーケンス(httpリクエストのような)の場合、手動クリーンアップが不要です(手動コントローラに加入と仮定)

「それが完了するシーケンスなら(うちの1つの値のシーケンス、http、laは1つです)

その無限列ならばあなたは解除すべきです非同期パイプがあなたのためにしています

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

ソース2

また、中角度2にRangleガイドそれは読み込み

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

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

これは、サブスクリプションがコンポーネントの内部で作成されたときに適用されますObservable

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

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

ソース3

私たちが見れば、この例と同じRangleから我々がいることがわかります導くSubscriptionにはroute.params必要ないunsubscribe()ものはときに我々は知らないので、params変更(新しい値を放出する)を停止します。

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


15
呼び出しcomplete()自体は、サブスクリプションをクリーンアップするようには見えません。どのように呼び出したnext()後でcomplete()も、takeUntil()シーケンスが終了したときではなく、値が生成されたときにのみ停止すると思います。
Firefly

2
@seangwrightタイプのメンバーと簡単なテストSubjectコンポーネント内にし、それを切り替えるngIfトリガーにngOnInitしてngOnDestroyショー、(フックアップの対象とそのサブスクリプションは、完全なんかに配置され得ることをfinallyサブスクリプションに演算子はを)。で呼び出す必要Subject.complete()ngOnDestroyあるので、サブスクリプションは自分でクリーンアップできます。
Lars

3
あなた---編集3は、おかげで非常に洞察に満ちています!私はフォローアップの質問をしているだけです。takeUnitlアプローチを使用する場合、オブザーバブルから手動で登録解除する必要はありませんか?それは事実ですか?さらに、なぜ我々は呼び出す必要がないnext()中でngOnDestroy、なぜだけ呼び出しませんかcomplete()
uglycode 2017

6
@seangwrightそれは残念です。追加のボイラープレートは迷惑です。
スポンサック

3
イベント3については、medium.com
@ benlesh /

90

多数のサブスクリプションを用意し、手動でサブスクライブを解除する必要はありません。上司のようにサブスクリプションを処理するには、SubjecttakeUntilコンボを使用します。

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がコメントで提案した代替アプローチでは、takeUntilではなくtakeWhileを使用します。あなたはそれを好むかもしれませんが、この方法ではコンポーネントの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
  }
}

1
ブールを使用して状態を維持する場合、「takeUntil」を期待どおりに機能させるにはどうすればよいですか?
Val

5
私が使用しての間に有意な差があると思いtakeUntilとはtakeWhile。前者は起動されるとすぐにソースオブザーバブルからサブスクライブ解除しますが、後者はソースオブザーバブルによって次の値が生成されるとすぐにサブスクライブ解除します。ソースオブザーバブルによる値の生成がリソースを消費する操作である場合、2つの中から1つを選択すると、スタイルの設定を超える場合があります。プランクを
Alex Che

1
@AlexChe興味深いplunkを提供してくれてありがとう!これはtakeUntilvsの一般的な使用法としては非常に有効なポイントですtakeWhileが、特定のケースではそうではありません。コンポーネントの破棄でリスナーをサブスクライブ解除する必要がある場合は、のよう() => aliveにブール値をチェックするだけなtakeWhileので、時間/メモリを消費する操作は使用されず、スタイリング(ofc、この特定の場合)についてはほとんど違いがあります。
メタメーカー2017

1
@metamaker言う、私たちのコンポーネントでサブスクライブするでObservable、内部で暗号通貨nextをマイニングし、すべてのマイニングされたコインに対してイベントを発生させます。そのようなコインの1つをマイニングするには1日かかります。ではtakeUntil、私たちは、ソースの採掘から解除されますObservableすぐに一度ngOnDestroy、当社の部品の破壊時に呼び出されます。したがって、マイニングObservable機能は、このプロセス中にその操作をすぐにキャンセルできます。
Alex Che

1
私たちが使用している場合OTOH、 takeWhile、にngOnDestory私たちはブール変数を設定します。ただし、マイニングObservable機能は最大1日間は機能し続ける可能性があり、そのnext呼び出しの間にのみ、アクティブなサブスクリプションがなく、キャンセルする必要があることがわかります。
Alex Che

76

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

Observableの実行など、使い捨てのリソースを表します。Subscriptionには、引数をとらず、サブスクリプションが保持するリソースを破棄するだけの重要なメソッドである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();
  }
}

私はこのアプローチを使用しています。受け入れられた答えのように、これがtakeUntil()でアプローチを使用するよりも優れているかどうか疑問に思います。
マヌエルディイオリオ2017

私が知っている欠点はありません。これは良いとは思いません、ただ違うだけです。
スティーブンリケンズ2017

2
公式のアプローチと、サブスクリプションを収集して呼び出すというこのアプローチの詳細については、medium.com / @ benlesh / rxjs-dont-unsubscribe-6753ed4fda87を参照してください。(このアプローチは、私にはかなりすっきりしているように見えます。)takeUntilunsubscribe
Josh Kelley '29

3
この回答の小さな利点の1つ:this.subscriptionsnull かどうかを確認する必要がない
user2023861

1
sub = subsciption.add(..).add(..)多くの場合、予期しない結果が発生するため、addメソッドのチェーンを回避するだけですgithub.com/ReactiveX/rxjs/issues/2769#issuecomment-345636477
Evgeniy Generalov

32

Angularコンポーネント内のオブザーバブルのサブスクリプション解除に関するベストプラクティスの一部:

からの引用 Routing & Navigation

コンポーネントでオブザーバブルをサブスクライブする場合、ほとんどの場合、コンポーネントが破棄されたときにサブスクライブを解除するように手配します。

これが必要でない例外的なオブザーバブルがいくつかあります。ActivatedRouteオブザーバブルは例外です。

ActivatedRouteとそのオブザーバブルはルーター自体から隔離されています。ルーターは、ルーティングされたコンポーネントが不要になり、注入されたActivatedRouteがそれとともに終了したときに、コンポーネントを破棄します。

とにかく退会してください。それは無害であり、決して悪い習慣ではありません。

そして、次のリンクへの対応で:

Angularコンポーネント内のオブザーバブルのサブスクリプション解除に関するいくつかのベストプラクティスを収集して、あなたと共有しました:

  • http監視可能なサブスクリプション解除は条件付きであり、コンポーネントがケースバイケースで破棄された後に実行される「サブスクライブコールバック」の影響を考慮する必要があります。angularはサブスクライブを解除してhttpオブザーバブル自体をクリーンアップすることを知っています(1)(2)。これはリソースの観点からは真実ですが、話の半分しか伝えていません。直接電話をしているとしましょうhttpコンポーネント内からhttp。ユーザーがコンポーネントを閉じたので、応答に必要以上に時間がかかりました。のsubscribe()コンポーネントが閉じられて破棄されても、ハンドラーは呼び出されます。これは望ましくない副作用をもたらす可能性があり、さらに悪いシナリオでは、アプリケーションの状態が壊れたままになります。また、コールバック内のコードが破棄されたばかりのものを呼び出そうとすると、例外が発生する可能性もあります。ただし、同時に必要になることもあります。たとえば、電子メールクライアントを作成していて、電子メールの送信が完了したときにサウンドをトリガーするとしますコンポーネントが閉じている場合でも、それを発生させたいとします(8)。
  • 完了またはエラーが発生するオブザーバブルの登録を解除する必要はありません。ただし、そうしても害はありません。(7)
  • 使用する AsyncPipeコンポーネントの破壊時にオブザーバブルから自動的にサブスクライブ解除されるため、可能な限りして。
  • 親/ホストコンポーネントが存在する限り何度もサブスクライブされるActivatedRoute可能性がroute.paramsあるため、オブザーバブルを、ネストされた(コンポーネントセレクターでtpl内に追加された)または動的コンポーネント内でサブスクライブする場合のように、サブスクライブを解除します。上記の引用で述べたように、他のシナリオでそれらの購読を解除する必要はありませんRouting & Navigationドキュメント。
  • たとえば、コンポーネントが初期化されている限り複数回サブスクライブされる可能性があるため、Angularサービスを通じて公開されているコンポーネント間で共有されるグローバルオブザーバブルのサブスクリプションを解除します。
  • アプリケーションスコープサービスの内部オブザーバブルをサブスクライブ解除する必要はありません。このサービスは破棄されないためです。アプリケーション全体が破棄されない限り、サブスクライブを解除する本当の理由はなく、メモリリークの可能性はありません。(6)

    注:スコープサービス、つまりコンポーネントプロバイダーについては、コンポーネントが破棄されると破棄されます。この場合、このプロバイダー内のオブザーバブルをサブスクライブする場合は、OnDestroyドキュメントによると、サービスが破棄されたときに呼び出されるライフサイクルフックをがあります。
  • サブスクリプション解除により発生する可能性のあるコードの混乱を回避するには、抽象的な手法を使用してください。takeUntil (3)を使用してサブスクリプションを管理するか、(4)AngularのObservablesからサブスクリプションを解除する最も簡単な方法で説明されているこのnpm パッケージを使用できます。
  • とのFormGroupようなオブザーバブルから常にサブスクライブを解除しますform.valueChangesform.statusChanges
  • 次のRenderer2ようなサービスのオブザーバブルから常にサブスクライブを解除しますrenderer2.listen
  • Angular Docsがサブスクライブ解除する必要のないオブザーバブルを明示的に通知するまで、メモリリークガードステップとしてオブザーバブルのすべてからサブスクライブを解除します(問題を確認してください:(5)RxJS Unsubscribing(Open)のドキュメント)。
  • おまけ:常にAngularの方法を使用してイベントをバインドしますHostListener。Angleは、必要に応じてイベントリスナーを削除することを考慮し、イベントバインドによる潜在的なメモリリークを防止します。

最後のヒント:オブザーバブルが自動的にサブスクライブ解除/完了しているかどうかがわからない場合は、completeコールバックを追加subscribe(...)し、コンポーネントが破棄されたときに呼び出されるかどうかを確認します。


6番の答えは正しくありません。サービスは破棄ngOnDestroyされ、サービスがルートレベル以外のレベルで提供されたときに呼び出されます。たとえば、後で削除されるコンポーネントで明示的に提供されます。これらの場合は、サービスの内部オブザーバブルの
サブスクリプション

@Drenai、コメントに感謝し、礼儀正しくは同意しません。コンポーネントが破棄されると、コンポーネント、サービス、およびオブザーバブルはすべてGCされ、コンポーネントから離れた場所にオブザーバブルの参照を保持しない限り、サブスクリプション解除は役に立たなくなります(コンポーネントの状態をグローバルにリークすることは論理的ではありません)サービスをコンポーネントにスコープしているにもかかわらず)
Mouneer '11 / 09/18

破棄されるサービスに、DI階層の上位にある別のサービスに属するオブザーバブルへのサブスクリプションがある場合、GCは発生しません。中に退会することによって、このシナリオを避けるためngOnDestroy、サービスが破壊されたときに必ず呼び出されるgithub.com/angular/angular/commit/...
Drenai

1
@Drenai、更新された回答を確認してください。
Mouneer、2018年

2
@Timまず第一に、Feel free to unsubscribe anyway. It is harmless and never a bad practice.そしてあなたの質問に関して、それは異なります。子コンポーネントが複数回開始された場合(たとえば、内部に追加された、ngIfまたは動的にロードされた場合)、同じオブザーバーに複数のサブスクリプションが追加されないように、サブスクライブを解除する必要があります。それ以外の場合は必要ありません。ただし、子コンポーネント内でのサブスクライブを解除することをお勧めします。これにより、子コンポーネントが再利用可能になり、使用方法から分離されます。
Mouneer

18

場合によります。を呼び出す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()ます。


6

Angular 2の公式ドキュメントには、登録を解除するタイミングと無視しても問題がない場合の説明が記載されています。このリンクを見てください:

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

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

サブスクリプションをキャプチャし、AstronautComponentが破棄されたときにサブスクリプションを解除することに注意してください。これはメモリリークガードステップです。AstronautComponentのライフタイムはアプリ自体のライフタイムと同じであるため、このアプリには実際のリスクはありません。これは、より複雑なアプリケーションでは必ずしも当てはまりません。

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

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


3
コンポーネントとしては、自分が子供であるかどうかは決してわかりません。したがって、ベストプラクティスとして、サブスクリプションを常に解除する必要があります。
SeriousM 2016年

1
MissionControlComponentに関するポイントは、それが親であるかどうかではなく、コンポーネント自体がサービスを提供するということです。MissionControlが破壊されると、サービスとサービスのインスタンスへの参照も破壊されるため、リークの可能性はありません。
2016年

6

ベース:クラス継承を使用して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));
    }
}


1
これは正しく機能しません。このソリューションを使用するときは注意してください。this.componentDestroyed$.next()上記の
Sean

4

公式の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;
  }
}

これは、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');
  }
}

デモプランカーはこちら

追記: Re Edit 3-'Official' Solution、これはサブスクリプションの前に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

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

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

これを使用するには、角度コンポーネントで拡張し、subscribe()次のようにメソッドを呼び出します。

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

また、通常どおりエラーと完了のコールバック、オブザーバーオブジェクトを受け入れるか、まったくコールバックしません。super.ngOnDestroy()子コンポーネントにそのメソッドも実装している場合は、必ず呼び出してください。

Ben Leshによる追加のリファレンスをここで見つけてくださいRxJS:Do n'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

私は最後の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() {}
}

:あなたは「この」バインドするために、矢印の機能を使用する必要があるthis.ngOnDestroy = () => { f.bind(this)(); this.componentDestroyed$.complete(); };
Damsorian

2

登録解除が必要な場合は、監視可能なパイプメソッドの次の演算子を使用できます

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メソッドをラップします。

重要:オペレーターは、監視可能なパイプの最後のオペレーターでなければなりません。


これはうまく機能しましたが、angular 9にアップグレードすると効果がなくなるようです。誰もが理由を知っていますか?
ymerej

1

通常、コンポーネントが破棄されたときにサブスクライブを解除する必要がありますが、Angularは、たとえば、Angular4の新しいマイナーバージョンでは、サブスクライブをルーティングするための次のセクションを備えているため、処理するにつれてますますそれを処理します。

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

「ルーティングとナビゲーション」ページの「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();
  }
}

3
混乱しています。ここで何言ってるの?あなた(Angularの最近のドキュメント/ノート)は、Angularがそれを処理し、後で登録解除が適切なパターンであることを確認すると言っているようです。ありがとう。
jamie

1

上記の状況に対するもう1つの短い追加は次のとおりです。

  • サブスクライブされたストリームの新しい値が必要なくなった場合、または問題にならない場合は、常にサブスクライブを解除してください。トリガーの数が少なくなり、いくつかのケースでパフォーマンスが向上します。サブスクライブされたデータ/イベントが存在しないコンポーネントや、まったく新しいストリームへの新しいサブスクリプションが必要な場合(更新など)は、サブスクリプション解除の良い例です。

0

ngOnDestroy関数(angular lifeCycle)のSPAアプリケーションで購読ごとに、購読解除する必要があります。Advantage =>状態が重くなりすぎないようにします。

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

コンポーネント2:

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」クラスを使用します。

これが購読者クラスです。

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

最新のSubscriptionクラスを使用すると、それほど厄介なコードではなく、Observableのサブスクライブを解除できます。

私たちはこれを行うことができますnormal variableが、それはoverride the last subscriptionすべての新しいサブスクライブで行われるため、それを避けてください。このアプローチは、より多くのObseravableを処理する場合BehavoiurSubjectや、Subject

申し込み

Observableの実行など、使い捨てのリソースを表します。Subscriptionには、引数をとらず、サブスクリプションが保持しているリソースを破棄するだけの重要なメソッド、unsubscribeがあります。

これは2つの方法で使用できます。

  • サブスクリプションをサブスクリプション配列に直接プッシュできます

     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());
      }
    
  • 使用してadd()Subscription

    subscriptions = new Subscription();
    
    this.subscriptions.add(subscribeOne);
    this.subscriptions.add(subscribeTwo);
    
    ngOnDestroy() {
      this.subscriptions.unsubscribe();
    }
    

A Subscriptionは子のサブスクリプションを保持し、それらすべてを安全にサブスクライブ解除できます。このメソッドは、考えられるエラーを処理します(たとえば、子サブスクリプションがnullの場合)。

お役に立てれば.. :)


0

SubSinkパッケージ:登録解除のための簡単で一貫性のあるソリューション

他には誰も触れていないので、Ward Bellが作成したSubsinkパッケージをお勧めします:https : //github.com/wardbell/subsink#readme

私たちはプロジェクトでそれを使用してきました。あらゆる状況で機能する一貫した方法があることは、多くの助けになります。


0

結果を出力した直後に完了するオブザーバブルAsyncSubjectや、たとえばhttpリクエストからのオブザーバブルなどの場合、登録を解除する必要はありません。unsubscribe()それらを呼び出すことは害にはなりませんが、オブザーバブルがclosedunsubscribeメソッドである場合、単に何もしません

if (this.closed) {
  return;
}

時間の経過とともにいくつかの値を出力する長期のオブザーバブル(たとえば、a BehaviorSubjectまたはa ReplaySubject)がある場合、メモリリークを防ぐためにサブスクライブを解除する必要があります。

パイプオペレーターを使用して、このような長命のオブザーバブルから結果を出力した直後に完了するオブザーバブルを簡単に作成できます。ここでいくつかの回答でtake(1)パイプが言及されています。しかし、私first()パイプが好きです。との違いtake(1)は、次のとおりです。

EmptyError次の通知が送信される前にオブザーバブルが完了した場合は、オブザーバーのエラーコールバックにを配信します。

最初のパイプのもう1つの利点は、特定の条件を満たす最初の値を返すのに役立つ述語を渡すことができることです。

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は、最初の値を発行した直後に(または関数の引数を渡すときに、述語を満たす最初の値を)完了するので、サブスクライブを解除する必要はありません。

観測可能な寿命が長いかどうかわからない場合もあります。私はそれが良い習慣だと言っているfirstわけではありませんが、手動で購読を解除する必要がないことを確認するためだけに、いつでもパイプを追加することができます。first1つの値のみを放出するオブザーバブルにパイプを追加しても問題はありません。

開発中に、あなたは使うことができ、パイプソース観察できるが、いくつかのイベントを発する場合に失敗します。これは、オブザーバブルのタイプと、サブスクライブを解除する必要があるかどうかを調べるのに役立ちます。single

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

firstそしてsingle、両方のパイプは、オプションの述語を取ることができますが、違いは重要であり、きれいにまとめ、非常に似たようで、ここで、このstackoverflowの答え

最初

最初のアイテムが現れるとすぐに放出します。その直後に完了します。

シングル

ソースオブザーバブルがいくつかのイベントを発行すると失敗します。


注意: 公式ドキュメントを参照して、私の回答ではできるだけ正確かつ完全になるように努めましたが、重要なものが欠けている場合はコメントしてください...


-1

--- Angular 9およびRxjs 6ソリューションを更新

  1. 使用unsubscribengDestroyの角度成分のライフサイクル
class SampleComponent implements OnInit, OnDestroy {
  private subscriptions: Subscription;
  private sampleObservable$: Observable<any>;

  constructor () {}

  ngOnInit(){
    this.subscriptions = this.sampleObservable$.subscribe( ... );
  }

  ngOnDestroy() {
    this.subscriptions.unsubscribe();
  }
}
  1. takeUntilRxjsでの使用
class SampleComponent implements OnInit, OnDestroy {
  private unsubscribe$: new Subject<void>;
  private sampleObservable$: Observable<any>;

  constructor () {}

  ngOnInit(){
    this.subscriptions = this.sampleObservable$
    .pipe(takeUntil(this.unsubscribe$))
    .subscribe( ... );
  }

  ngOnDestroy() {
    this.unsubscribe$.next();
    this.unsubscribe$.complete();
  }
}
  1. そのときに呼び出すいくつかのアクションはngOnInit、コンポーネントが初期化されるときに一度だけ発生します。
class SampleComponent implements OnInit {

  private sampleObservable$: Observable<any>;

  constructor () {}

  ngOnInit(){
    this.subscriptions = this.sampleObservable$
    .pipe(take(1))
    .subscribe( ... );
  }
}

asyncパイプもあります。しかし、これはテンプレートで使用されます(Angularコンポーネントでは使用されません)。


最初の例は不完全です。
Paul-Sebastian Manole
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.