AngularJS: ¿$ destroy elimina oyentes de eventos?

200

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

Al escuchar este evento, puede eliminar oyentes de eventos que pueden causar pérdidas de memoria. Los oyentes registrados en ámbitos y elementos se limpian automáticamente cuando se destruyen, pero si registró un oyente en un servicio, o registró un oyente en un nodo DOM que no se está eliminando, tendrá que limpiarlo usted mismo o corre el riesgo de presentar una pérdida de memoria.

Mejor práctica: las directivas deben limpiarse después de sí mismas. Puede usar element.on ('$ destroy', ...) o scope. $ On ('$ destroy', ...) para ejecutar una función de limpieza cuando se elimina la directiva.

Pregunta:

Tengo element.on "click", (event) ->dentro de mi directiva:

  1. Cuando se destruye la directiva, ¿hay alguna referencia de memoria element.onpara evitar que se recolecte basura?
  2. La documentación angular establece que debería usar un controlador para eliminar los oyentes de eventos en el $destroyevento emitido. Tenía la impresión de que destroy()eliminó los oyentes de eventos, ¿no es este el caso?
dman
fuente

Respuestas:

433

Oyentes de eventos

En primer lugar, es importante comprender que hay dos tipos de "oyentes de eventos":

  1. Oyentes de eventos de alcance registrados a través de $on:

    $scope.$on('anEvent', function (event, data) {
      ...
    });
  2. Controladores de eventos adjuntos a elementos mediante, por ejemplo, ono bind:

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

$ scope. $ destroy ()

Cuando $scope.$destroy()se ejecute, eliminará todos los oyentes registrados a través $onde ese $ scope.

Será no eliminar elementos DOM o cualquier controlador de eventos adjuntos de la segunda clase.

Esto significa que llamar $scope.$destroy()manualmente desde el ejemplo dentro de la función de enlace de una directiva no eliminará un controlador adjunto a través de, por ejemplo element.on, ni el elemento DOM en sí.


element.remove ()

Tenga en cuenta que removees un método jqLite (o un método jQuery si jQuery se carga antes que AngularjS) y no está disponible en un objeto de elemento DOM estándar.

Cuando element.remove()se ejecuta ese elemento y todos sus elementos secundarios se eliminarán del DOM juntos, todos los controladores de eventos se adjuntarán a través de, por ejemplo element.on.

Será no destruir el alcance $ asociado con el elemento.

Para hacerlo más confuso también hay un evento jQuery llamado $destroy. A veces, cuando se trabaja con bibliotecas jQuery de terceros que eliminan elementos, o si las elimina manualmente, es posible que deba realizar una limpieza cuando eso suceda:

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

Qué hacer cuando una directiva es "destruida"

Esto depende de cómo se "destruye" la directiva.

Un caso normal es que una directiva se destruye porque ng-viewcambia la vista actual. Cuando esto suceda, la ng-viewdirectiva destruirá el $ alcance asociado, separe todas las referencias a su alcance principal y llame remove()al elemento.

Esto significa que si esa vista contiene una directiva con esto en su función de enlace cuando es destruida por ng-view:

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

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

Ambos oyentes de eventos se eliminarán automáticamente.

Sin embargo, es importante tener en cuenta que el código dentro de estos oyentes aún puede causar pérdidas de memoria, por ejemplo, si ha logrado el patrón común de pérdida de memoria JS circular references.

Incluso en este caso normal de una directiva que se destruye debido a un cambio de vista, hay cosas que puede necesitar limpiar manualmente.

Por ejemplo, si ha registrado un oyente en $rootScope:

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

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

Esto es necesario ya $rootScopeque nunca se destruye durante la vida útil de la aplicación.

Lo mismo ocurre si está utilizando otra implementación de pub / sub que no realiza automáticamente la limpieza necesaria cuando se destruye el $ scope, o si su directiva transfiere devoluciones de llamada a los servicios.

Otra situación sería cancelar $interval/ $timeout:

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

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

Si su directiva adjunta controladores de eventos a elementos, por ejemplo, fuera de la vista actual, también debe limpiarlos manualmente:

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

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

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

Estos fueron algunos ejemplos de qué hacer cuando las directivas son "destruidas" por Angular, por ejemplo por ng-viewo ng-if.

Si tiene directivas personalizadas que administran el ciclo de vida de los elementos DOM, etc., por supuesto, se volverá más complejo.

tasseKATT
fuente
44
'$ rootScope nunca se destruye durante la vida útil de la aplicación'. : obvio una vez que lo piensas. Eso es lo que me faltaba.
user276648
@tasseKATT Una pequeña pregunta aquí, si en el mismo controlador tenemos múltiples $ rootScope. $ on para diferentes eventos, entonces llamaremos a $ scope. $ on ("$ destroy", ListenerName1); por cada $ rootScope. $ en diferente ??
Yashika Garg
2
@YashikaGarg Probablemente sería más fácil tener una función auxiliar que llame a todos los oyentes. Como $ scope. $ On ('$ destroy'), function () {ListenerName1 (); ListenerName2 (); ...}); ¿Hay alguna complejidad adicional para $ en controladores de eventos en ámbitos no aislados? ¿O aislar ámbitos con enlaces bidireccionales?
David Rice
¿Por qué registrar oyentes de eventos en $ rootscope? Registro los escuchas de eventos en $ scope y luego otros controladores realizan $ rootscope.broadcast ('eventname') y mis escuchas de eventos se ejecutan. ¿Estos oyentes de eventos en $ scope que están escuchando eventos de aplicaciones todavía se van a limpiar automáticamente?
Skychan
@Skychan Lo siento, me perdí tu comentario. Esto es una suposición, pero la gente podría usarlo $rootScopedebido a esto: stackoverflow.com/questions/11252780/... Tenga en cuenta que, como la respuesta indica en la parte superior, esto ha cambiado. Sí, los oyentes de eventos normales $scopese limpiarán automáticamente cuando se destruya ese alcance.
tasseKATT