AngularJS-$ destroyはイベントリスナーを削除しますか?


200

https://docs.angularjs.org/guide/directive

このイベントをリッスンすることで、メモリリークを引き起こす可能性のあるイベントリスナーを削除できます。スコープと要素に登録されたリスナーは、破棄されるときに自動的にクリーンアップされますが、サービスにリスナーを登録した場合、または削除されていないDOMノードにリスナーを登録した場合は、自分でクリーンアップするか、メモリリークが発生するリスクがあります。

ベストプラクティス:ディレクティブは自分自身でクリーンアップする必要があります。element.on( '$ destroy'、...)またはscope。$ on( '$ destroy'、...)を使用して、ディレクティブが削除されたときにクリーンアップ関数を実行できます。

質問:

私はelement.on "click", (event) ->私のディレクティブの中にあります:

  1. ディレクティブが破棄されると、element.onガベージコレクションされないようにするためのへのメモリ参照はありますか?
  2. Angularドキュメントには、ハンドラーを使用して、発行されたイベントのイベントリスナーを削除する必要があると記載$destroyされています。destroy()イベントリスナーを削除したような印象を受けましたが、そうではありませんか?

回答:


433

イベントリスナー

まず、2種類の「イベントリスナー」があることを理解することが重要です。

  1. を介して登録されたスコープイベントリスナー$on

    $scope.$on('anEvent', function (event, data) {
      ...
    });
  2. onまたはを介して要素に接続されたイベントハンドラーbind

    element.on('click', function (event) {
      ...
    });

$ scope。$ destroy()

ときに$scope.$destroy()実行され、それは経由して登録されているすべてのリスナーが削除されます$onその$の範囲をを。

DOM要素や、第2種の添付イベントハンドラーは削除されません

つまり$scope.$destroy()、ディレクティブのリンク関数内の例から手動で呼び出してelement.onも、たとえばを介して接続されたハンドラーやDOM要素自体は削除されません。


element.remove()

removeはjqLit​​eメソッド(またはjQueryがAngularjSの前にロードされている場合はjQueryメソッド)であり、標準のDOM Elementオブジェクトでは使用できないことに注意してください。

ときにelement.remove()その要素を実行され、そのすべての子が一緒にDOMから削除されるすべてのイベントハンドラは、例えば介して結合しますelement.on

要素に関連付けられている$ scope 破棄されません

さらに混乱させるために、というjQueryイベントもあり$destroyます。要素を削除するサードパーティのjQueryライブラリを使用している場合、または要素を手動で削除した場合、それが発生したときにクリーンアップを実行する必要がある場合があります。

element.on('$destroy', function () {
  scope.$destroy();
});

ディレクティブが「破棄された」場合の対処法

これは、ディレクティブがどのように「破棄される」かによって異なります。

通常のケースではng-view、現在のビューを変更するためにディレクティブが破棄されます。これが発生すると、ng-viewディレクティブは関連する$ scopeを破棄し、その親スコープへのすべての参照を切断remove()して、要素を呼び出します。

これは、次のようにして破棄されたときに、そのビューのリンク関数にthisを含むディレクティブが含まれている場合を意味しますng-view

scope.$on('anEvent', function () {
 ...
});

element.on('click', function () {
 ...
});

両方のイベントリスナーは自動的に削除されます。

ただし、これらのリスナー内のコードは、たとえば一般的なJSメモリリークパターンを達成した場合など、メモリリークを引き起こす可能性があることに注意することが重要circular referencesです。

ビューの変更によりディレクティブが破壊されるというこの通常のケースでも、手動でクリーンアップする必要があるかもしれません。

たとえば、リスナーを次のように登録したとします$rootScope

var unregisterFn = $rootScope.$on('anEvent', function () {});

scope.$on('$destroy', unregisterFn);

$rootScopeアプリケーションの存続期間中に破棄されることはないため、これは必要です。

$ scopeが破棄されたときに必要なクリーンアップを自動的に実行しない別のpub / sub実装を使用している場合、またはディレクティブがサービスにコールバックを渡す場合も同様です。

別の状況は、キャンセルすることです$interval/ $timeout

var promise = $interval(function () {}, 1000);

scope.$on('$destroy', function () {
  $interval.cancel(promise);
});

ディレクティブが、たとえば現在のビューの外の要素にイベントハンドラーをアタッチする場合は、それらも手動でクリーンアップする必要があります。

var windowClick = function () {
   ...
};

angular.element(window).on('click', windowClick);

scope.$on('$destroy', function () {
  angular.element(window).off('click', windowClick);
});

これらは、たとえばng-viewやなど、Angularによってディレクティブが「破棄される」ときに何をするかの例ですng-if

DOM要素のライフサイクルなどを管理するカスタムディレクティブがある場合は、もちろんより複雑になります。


4
'$ rootScopeは、アプリケーションの存続期間中に破棄されることはありません。' :考えれば明らかです。それは私が欠けていたものです。
user276648

@tasseKATTここで小さな質問です。同じコントローラ内に異なるイベントに対して複数の$ rootScope。$ onがある場合、$ scope。$ on( "$ destroy"、ListenerName1);を呼び出します。$ rootScope。$ onごとに異なる??
Yashika Garg

2
@YashikaGargすべてのリスナーを呼び出すヘルパー関数を用意するのがおそらく最も簡単でしょう。$ scope。$ on( '$ destroy')のように、function(){ListenerName1(); ListenerName2(); ...}); 非分離スコープの$ onイベントハンドラーに追加の複雑さはありますか?または、双方向バインディングでスコープを分離しますか?
David Rice

$ rootscopeにイベントリスナーを登録する理由 $ scopeにイベントリスナーを登録すると、他のコントローラーが$ rootscope.broadcast( 'eventname')を実行し、イベントリスナーが実行されます。アプリケーションイベントをリッスンしている$ scopeのこれらのイベントリスナーは、まだ自動クリーニングされますか?
Skychan、2015年

@Skychanすみませんコメントを逃しました。これは推測ですが、人々が使用する可能性があり$rootScope、このため:stackoverflow.com/questions/11252780/...上部の回答の状態として、これが変更されたことを注意します。はい、$scopeそのスコープが破棄されると、通常のイベントリスナーは自動的にクリーンアップされます。
tasseKATT
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.