Angular/RxJS `Subscription` からいつ退会すればよいですか? 質問する

Angular/RxJS `Subscription` からいつ退会すればよいですか? 質問する

ライフサイクル中にSubscriptionインスタンスを保存して呼び出す必要があるのはいつですか? また、単に無視できるのはいつですか?unsubscribe()ngOnDestroy

すべてのサブスクリプションを保存すると、コンポーネント コードがかなり混乱します。

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

ベストアンサー1

要約

この質問では、有限値と無限値という 2 種類の Observable が存在します。

httpObservable は有限(1) の値を生成し、DOM イベント リスナー Observable のようなものは無限の値を生成します。

手動で呼び出す場合subscribe(非同期パイプを使用せず)、 Observable は無限unsubscribeになります。

有限のものについては心配する必要はありません。RxJ が処理します。


出典:

  1. 私はAngularのGitterでRob Wormaldからの回答を見つけましたここ

    彼は次のように述べています (わかりやすくするために再構成し、強調は私によるものです)。

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

    「それが完了するシーケンスである場合」と言うべきでしょう(HTTP のような単一値シーケンスもその 1 つです)

    無限シーケンスの場合は、非同期パイプがサブスクライブを解除する必要があります。

    また彼は次のように述べているこのYouTube動画「自分自身でクリーンアップする」 Observable について、完了するObservable のコンテキストで(Promise のように、常に 1 つの値を生成して終了するため、常に完了します。Promise が XHR イベント リスナーをクリーンアップすることを確認するために、Promise からサブスクライブ解除することを心配したことは一度もありませんよね?)

  2. また、Angular 2 の Rangle ガイドそれは読む

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

    「当社のObservable有効期間はサブスクリプションよりも長い」というフレーズはいつ適用されますか?

    これは、Observable が完了する前(またはそれより少し前)に破棄されるコンポーネント内でサブスクリプションが作成される場合に適用されます。

    httpこれは、10 個の値を発行するリクエストまたは Observableをサブスクライブし、そのリクエストが返される前または 10 個の値が発行される前にコンポーネントが破棄された場合でも、問題ないという意味だとhttp解釈しました。

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

  3. 見てみるとこの例route.params同じ Rangle ガイドから、 へのサブスクリプションにはが必要であることがわかります。これは、 がいつ変更されなくなるか (新しい値を発行するか)unsubscribe()がわからないためです。params

    コンポーネントは別の場所に移動することで破棄される可能性があります。その場合、ルート パラメータは変更されたままになる可能性があります (技術的にはアプリが終了するまで変更される可能性があります)。また、サブスクリプションで割り当てられたリソースは、完了がないため、引き続き割り当てられます。

  4. このビデオNgEuropeのRob Wormald氏も、Router Observablesの購読を解除する必要はないと述べています。彼はまた、httpサービスActivatedRoute.paramsについても言及しており、このビデオ2016年11月から。

  5. Angularチュートリアル、ルーティングの章現在、次の通り述べています。

    Router、提供する観測可能オブジェクトを管理し、サブスクリプションをローカライズします。サブスクリプションはコンポーネントが破棄されるとクリーンアップされ、メモリ リークから保護されるため、ルートからサブスクリプションを解除する必要はありませんparams Observable

    こちらは議論Router Observables に関する Angular ドキュメントの GitHub Issues で、Ward Bell は、これらすべてについての説明が進行中であると述べています。


私は NGConf で Ward Bell とこの質問について話し合いました (彼にこの回答を見せましたが、彼はそれが正しいと言いました)。しかし彼は、Angular のドキュメント チームがこの質問に対する未公開の解決策を持っている (ただし、承認を得るための作業中) と言いました。彼はまた、近々発表される公式の推奨事項に合わせて SO の回答を更新できるとも言いました。

今後私たち全員が使用すべき解決策は、クラス コード内で Observable を呼び出すprivate ngUnsubscribe = new Subject<void>();すべてのコンポーネントにフィールドを追加することです。.subscribe()

次にメソッドを呼び出しthis.ngUnsubscribe.next(); this.ngUnsubscribe.complete();ますngOnDestroy()

秘密のソース(すでに述べたようにメタメーカー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<void>();

    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演算子チェーン内の中間 Observable によるリークを防ぐために、演算子を最後に追加することが重要です。


最近では、Angular の冒険Ben Lesh と Ward Bell が、コンポーネントでいつどのように登録解除するかという問題について話し合います。この話し合いは 1:05:30 頃から始まります。

Ward 氏は「現在、多くの機械を必要とするひどい takeUntil ダンスがあります」と述べ、Shai Reznik 氏は「Angular は、http やルーティングなどのサブスクリプションの一部を処理します」と述べています。

これに応えて、Ben は、Observable を Angular コンポーネントのライフサイクル イベントにフックできるようにする議論が現在行われていると述べ、Ward は、コンポーネントの内部状態として維持される Observable をいつ完了するかを知る方法として、コンポーネントがサブスクライブできるライフサイクル イベントの Observable を提案しています。

とはいえ、今必要なのは主に解決策なので、ここに他のリソースをいくつか紹介します。

  1. RxJs コア チーム メンバーの Nicholas Jamieson によるパターンの推奨事項takeUntil()と、それを強制するための TSLint ルール:https://ncjamieson.com/avoiding-takeuntil-leaks/

  2. thisコンポーネント インスタンス ( ) をパラメーターとして受け取り、 中に自動的に登録解除する Observable オペレーターを公開する軽量 npm パッケージngOnDestroy:https://github.com/NetanelBasal/ngx-take-until-destroy

  3. AOT ビルドを実行していない場合 (ただし、現在はすべて AOT を実行する必要があります)、上記のバリエーションで人間工学的に少し優れているものを以下に示します。ngx-rx-collector は、以下の URL からダウンロードできます。

  4. 非同期パイプのように機能するカスタム ディレクティブ*ngSubscribeですが、テンプレートに埋め込みビューを作成するので、テンプレート全体で「ラップされていない」値を参照できます。https://netbasal.com/diy-subscription-handling-directive-in-angular-c8f6e762697f

Nicholas のブログへのコメントで、 の過度の使用はtakeUntil()コンポーネントが多くのことをしようとしている兆候である可能性があり、既存のコンポーネントをFeature コンポーネントPresentation| asyncコンポーネントに分離することを検討する必要があると述べました。次に、 Observable を Feature コンポーネントから Presentation コンポーネントの に渡すことができます。Inputつまり、どこにもサブスクリプションは必要ありません。このアプローチの詳細については、こちらをご覧ください。ここ

おすすめ記事