AngularJS - $ destroy supprime-t-il les écouteurs d'événements?

200

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

En écoutant cet événement, vous pouvez supprimer les écouteurs d'événements susceptibles de provoquer des fuites de mémoire. Les écouteurs enregistrés dans les étendues et les éléments sont automatiquement nettoyés lorsqu'ils sont détruits, mais si vous avez enregistré un écouteur sur un service ou enregistré un écouteur sur un nœud DOM qui n'est pas supprimé, vous devrez le nettoyer vous-même ou vous risquez d'introduire une fuite de mémoire.

Meilleure pratique: les directives doivent se nettoyer après elles-mêmes. Vous pouvez utiliser element.on ('$ destroy', ...) ou scope. $ On ('$ destroy', ...) pour exécuter une fonction de nettoyage lorsque la directive est supprimée.

Question:

J'ai une element.on "click", (event) ->dans ma directive:

  1. Lorsque la directive est détruite, existe-t-il des références de mémoire à la element.onpour l'empêcher d'être récupérée?
  2. La documentation angulaire indique que je dois utiliser un gestionnaire pour supprimer les écouteurs d'événements sur l' $destroyévénement émis. J'avais l'impression d'avoir destroy()supprimé les auditeurs d'événements, n'est-ce pas le cas?
dman
la source

Réponses:

433

Auditeurs d'événements

Tout d'abord, il est important de comprendre qu'il existe deux types d '"écouteurs d'événements":

  1. Auditeurs d'événements Scope enregistrés via $on:

    $scope.$on('anEvent', function (event, data) {
      ...
    });
  2. Les gestionnaires d'événements attachés aux éléments via par exemple onou bind:

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

$ scope. $ destroy ()

Une fois $scope.$destroy()exécuté, il supprimera tous les écouteurs enregistrés via $onsur cette portée $.

Il ne supprimera pas les éléments DOM ou les gestionnaires d'événements attachés du deuxième type.

Cela signifie que l'appel $scope.$destroy()manuel à partir d'un exemple dans la fonction de lien d'une directive ne supprimera pas un gestionnaire attaché via par exemple element.on, ni l'élément DOM lui-même.


element.remove ()

Notez que remove s'agit d'une méthode jqLite (ou d'une méthode jQuery si jQuery est chargé avant AngularjS) et n'est pas disponible sur un objet élément DOM standard.

Quand element.remove()est exécuté cet élément et tous ses enfants seront supprimés du DOM ensemble tous les gestionnaires d'événements attachés via par exempleelement.on .

Il ne détruira pas la portée $ associée à l'élément.

Pour le rendre plus déroutant, il existe également un événement jQuery appelé $destroy. Parfois, lorsque vous travaillez avec des bibliothèques jQuery tierces qui suppriment des éléments, ou si vous les supprimez manuellement, vous devrez peut-être effectuer un nettoyage lorsque cela se produit:

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

Que faire lorsqu'une directive est "détruite"

Cela dépend de la façon dont la directive est "détruite".

Un cas normal est qu'une directive est détruite car elle ng-viewmodifie la vue actuelle. Lorsque cela se produit, la ng-viewdirective détruira la portée $ associée, coupera toutes les références à sa portée parent et appelleraremove() l'élément.

Cela signifie que si cette vue contient une directive avec ceci dans sa fonction de lien lorsqu'elle est détruite par ng-view:

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

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

Les deux écouteurs d'événements seront supprimés automatiquement.

Cependant, il est important de noter que le code à l'intérieur de ces écouteurs peut toujours provoquer des fuites de mémoire, par exemple si vous avez atteint le modèle de fuite de mémoire JS commun circular references.

Même dans ce cas normal où une directive est détruite en raison d'un changement de vue, vous devrez peut-être nettoyer manuellement certaines choses.

Par exemple, si vous avez enregistré un auditeur sur $rootScope:

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

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

Cela est nécessaire car il $rootScopen'est jamais détruit pendant la durée de vie de l'application.

Il en va de même si vous utilisez une autre implémentation pub / sub qui n'effectue pas automatiquement le nettoyage nécessaire lorsque la portée $ est détruite, ou si votre directive transmet des rappels aux services.

Une autre situation serait d'annuler $interval/ $timeout:

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

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

Si votre directive associe des gestionnaires d'événements à des éléments, par exemple en dehors de la vue actuelle, vous devez également les nettoyer manuellement:

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

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

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

Voici quelques exemples de ce qu'il faut faire lorsque des directives sont "détruites" par Angular, par exemple par ng-viewou ng-if.

Si vous avez des directives personnalisées qui gèrent le cycle de vie des éléments DOM, etc., cela deviendra bien sûr plus complexe.

tasseKATT
la source
4
'$ rootScope n'est jamais détruit pendant la durée de vie de l'application.' : évident quand on y pense. C'est ce qui me manquait.
user276648
@tasseKATT Une petite question ici, si dans le même contrôleur nous avons plusieurs $ rootScope. $ on pour différents événements, alors nous appellerons $ scope. $ on ("$ destroy", ListenerName1); pour chaque $ rootScope. $ différemment ??
Yashika Garg
2
@YashikaGarg Il serait probablement plus facile d'avoir juste une fonction d'aide qui appelle tous les auditeurs. Comme $ scope. $ On ('$ destroy'), function () {ListenerName1 (); ListenerName2 (); ...}); Existe-t-il une complexité supplémentaire pour $ on des gestionnaires d'événements sur des étendues non isolées? Ou isoler des étendues avec des liaisons bidirectionnelles?
David Rice
Pourquoi enregistrer des écouteurs d'événements sur $ rootscope? J'enregistre des écouteurs d'événements sur $ scope, puis d'autres contrôleurs exécutent $ rootscope.broadcast ('eventname') et mes écouteurs d'événements s'exécutent. Ces écouteurs d'événements sur $ scope qui écoutent les événements d'application vont-ils toujours être nettoyés automatiquement?
Skychan
@Skychan Désolé d'avoir raté votre commentaire. C'est une supposition, mais les gens pourraient utiliser à $rootScopecause de cela: stackoverflow.com/questions/11252780/… Notez que comme l'indique la réponse en haut, cela a été changé. Oui, les écouteurs d'événements normaux $scopeseront automatiquement nettoyés lorsque cette portée sera détruite.
tasseKATT du