AngularJS - $ destroy remove ouvintes de eventos?

200

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

Ao ouvir esse evento, você pode remover os ouvintes de eventos que podem causar vazamento de memória. Os ouvintes registrados nos escopos e nos elementos são limpos automaticamente quando são destruídos, mas se você registrou um ouvinte em um serviço ou um ouvinte em um nó DOM que não está sendo excluído, será necessário limpá-lo sozinho ou corre o risco de introduzir um vazamento de memória.

Prática recomendada: as diretivas devem ser limpas após elas próprias. Você pode usar element.on ('$ destroy', ...) ou scope. $ On ('$ destroy', ...) para executar uma função de limpeza quando a diretiva for removida.

Questão:

Eu tenho uma element.on "click", (event) ->dentro da minha diretiva:

  1. Quando a diretiva é destruída, existem referências de memória element.onpara impedir que ela seja coletada no lixo?
  2. A documentação angular indica que eu devo usar um manipulador para remover ouvintes de eventos no $destroyevento emitido. Fiquei com a impressão de que os destroy()ouvintes de eventos foram removidos, não é esse o caso?
dman
fonte

Respostas:

433

Ouvintes de eventos

Primeiro, é importante entender que existem dois tipos de "ouvintes de eventos":

  1. Ouvintes de eventos de escopo registrados via $on:

    $scope.$on('anEvent', function (event, data) {
      ...
    });
  2. Manipuladores de eventos anexados a elementos via, por exemplo, onou bind:

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

$ scope. $ destroy ()

Quando $scope.$destroy()é executado, ele remove todos os ouvintes registrados $onnesse escopo $.

Ele não removerá elementos DOM ou qualquer manipulador de eventos anexado do segundo tipo.

Isso significa que a chamada $scope.$destroy()manual do exemplo na função de link de uma diretiva não removerá um manipulador anexado por exemplo element.on, nem o próprio elemento DOM.


element.remove ()

Observe que removeé um método jqLite (ou um método jQuery se o jQuery for carregado antes do AngularjS) e não estiver disponível em um Objeto de Elemento DOM padrão.

Quando element.remove()é executado, esse elemento e todos os seus filhos serão removidos do DOM juntos, todos os manipuladores de eventos anexados via, por exemplo element.on.

Ele vai não destruir a $ âmbito associado ao elemento.

Para torná-lo mais confuso, também há um evento jQuery chamado $destroy. Às vezes, ao trabalhar com bibliotecas jQuery de terceiros que removem elementos ou se você os remove manualmente, pode ser necessário executar uma limpeza quando isso acontece:

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

O que fazer quando uma diretiva é "destruída"

Isso depende de como a diretiva é "destruída".

Um caso normal é que uma diretiva é destruída porque ng-viewaltera a exibição atual. Quando isso acontece, a ng-viewdiretiva destrói o escopo $ associado, interrompe todas as referências ao escopo pai e invoca remove()o elemento.

Isso significa que, se essa exibição contiver uma diretiva com isso em sua função de link, quando for destruída por ng-view:

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

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

Os dois ouvintes de eventos serão removidos automaticamente.

No entanto, é importante observar que o código dentro desses ouvintes ainda pode causar vazamentos de memória, por exemplo, se você atingiu o padrão de vazamento de memória JS comum circular references.

Mesmo nesse caso normal de uma diretiva ser destruída devido a uma alteração na exibição, há coisas que você pode precisar limpar manualmente.

Por exemplo, se você registrou um ouvinte em $rootScope:

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

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

Isso é necessário, pois $rootScopenunca é destruído durante a vida útil do aplicativo.

O mesmo acontece se você estiver usando outra implementação de publicação / publicação que não executa automaticamente a limpeza necessária quando o escopo $ é destruído ou se sua diretiva passa retornos de chamada para serviços.

Outra situação seria cancelar $interval/ $timeout:

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

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

Se sua diretiva anexar manipuladores de eventos a elementos, por exemplo, fora da visualização atual, você também precisará limpá-los manualmente:

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

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

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

Estes foram alguns exemplos do que fazer quando as diretivas são "destruídas" pelo Angular, por exemplo, por ng-viewou ng-if.

Se você possui diretivas personalizadas que gerenciam o ciclo de vida dos elementos DOM, etc., é claro que ficará mais complexo.

tasseKATT
fonte
4
'$ rootScope nunca é destruído durante a vida útil do aplicativo.' : óbvio quando você pensa sobre isso. Era isso que eu estava perdendo.
User276648
@tasseKATT Uma pequena pergunta aqui: se no mesmo controlador tivermos vários $ rootScope. $ on para eventos diferentes, chamaremos $ scope. $ on ("$ destroy", ListenerName1); para cada $ rootScope. $ diferente?
Yashika Garg
2
@YashikaGarg Provavelmente seria mais fácil ter apenas uma função auxiliar que chame todos os ouvintes. Como $ scope. $ On ('$ destroy'), function () {ListenerName1 (); ListenerName2 (); ...}); Existe alguma complexidade adicional para $ em manipuladores de eventos em escopos não isolados? Ou isolar escopos com ligações bidirecionais?
David Rice
Por que registrar ouvintes de eventos no $ rootscope? Registro ouvintes de evento no $ scope e, em seguida, outros controladores executam $ rootscope.broadcast ('eventname') e meus ouvintes de evento são executados. Esses ouvintes de evento no escopo $ que estão ouvindo eventos de aplicativo ainda serão limpos automaticamente?
Skychan
@ Skchan Desculpe, perdi o seu comentário. Isso é um palpite, mas as pessoas podem usar $rootScopepor causa disso: stackoverflow.com/questions/11252780/… Observe que, como a resposta indica na parte superior, isso foi alterado. Sim, os ouvintes de evento no normal $scopeserão limpos automaticamente quando esse escopo for destruído.
tasseKATT