angularjs:ngModel controller $ parser&删除元素后,$ formatter仍然会触发

时间:2014-07-11 15:49:16

标签: javascript angularjs angularjs-directive

在下面的例子中,我不明白为什么在删除元素后,角度仍然会解析解析器和格式化程序。我应该在指令中手动清理ngModel控制器吗?如果是这样,我该怎么做?

要了解我正在谈论的内容,请查看plunker,

  1. 打开控制台
  2. 点击'删除'按钮
  3. 点击“更改模式”'按钮
  4. 注意格式化程序仍在触发
  5. plunker:http://plnkr.co/edit/R7v5nB8JaQ91WcDGU8BC?p=preview

    JS

    angular.module('testMod', [])
    .controller('testCtrl', function($scope){
      $scope.test = "test";
      $scope.removeElem = function(id) {
        var elem = document.getElementById(id);
        angular.element(elem).remove();
      }
    }).directive('testDir',[function() {
      return {
        require: 'ngModel',
        scope:true,
        link: function(scope, elem, attr, ctrl) {
          console.log('in directive');
          ctrl.$parsers.unshift(function (newValue) {
              console.log('directive parser');
              return newValue;
          });
          ctrl.$formatters.unshift(function (newValue) {
              console.log('directive formatter');
              return newValue;
          });
        }
      }
    }]);
    

    HTML

    <body ng-controller='testCtrl'>
        <input id='test' test-dir ng-model='test'/>
        <button ng-click="removeElem('test')">remove</button>
        <button ng-click="test = test + 'a'">change model</button>
        <div>{{test}}</div>
    </body>
    

2 个答案:

答案 0 :(得分:4)

你的指令创建了自己的子范围 - 这是一件好事。它拥有所有的$ watch,并且当它的范围被破坏时应该自行清理。

优良作法是:

  1. 设置$ watch并计划稍后销毁范围时,创建子级或隔离范围。由于该指令创建了范围,因此它也应该在必要时销毁它并释放所有的$ watch。

  2. 检测元素何时被删除并在必要时销毁范围。有时,如果你的$ watch很重,那么这是有意义的,当你从DOM中删除绑定指令的元素时,你不希望它们徘徊。如果删除是暂时的,即切换元素的可见性

  3. ,这可能没有意义
  4. 指令永远不应该破坏属于另一个的范围。即如果你从父控制器继承了作用域,那么让父控制器自行清理 - 这不是child指令的工作。

  5. 如果指令设置了任何$ watch,它应该监听范围$ destroy事件,以便它可以取消注册它们。

  6. 如果指令已使用on()注册了任何javascript事件侦听器,则应在范围被销毁时使用off()注销它们。

  7. 通过处理 jQuery元素本身上的$ destroy事件,可以在从DOM中删除元素时执行清理:

    // creates a child scope owned by the directive
    scope: true,
    link: function(scope, element, attr) {
    
        // set up a click handler
        element.on('click', function() { 
            ...
        });
    
        // set up a $watch on the child scope. It returns a 
        // function that you can call to unregister the $watch
        var unregister = scope.$watch('test', function() {
             ...
        });
    
        // when the element is removed from the DOM, 
        // destroy the scope and all its $watches.
         element.on('$destroy', function() {
              // remove the $watch
              unregister();          
    
              // remove the click handler
              element.off('click');    
    
              // call $destroy on the child scope
              // so that it can propagate to any children
              scope.$destroy();
         });
    }
    

    不应该必须在ng-model之后清理。当$ destroy事件传播到子范围时,它将自行清理。如果所有指令都在自己清理完之后,那么在内存泄漏和孤立的$ watch时会有更多的担心。

    这是一个更新的Plunker,可在删除元素时清除其$ watch。

答案 1 :(得分:-1)

  

根据AngularJS文档

     

我们注册了一个事件element.on('$ destroy',...)。是什么激起了这个   $ destroy event?

     

AngularJS会发出一些特殊事件。当一个DOM节点   已经使用Angular的编译器编译的代码将被销毁,它会发出   $ destroy事件。类似地,当AngularJS范围被销毁时,它   向监听范围广播$ destroy事件。

     

通过侦听此事件,您可以删除可能的事件侦听器   导致内存泄漏。注册到范围和元素的听众是   当它们被摧毁时自动清理,但如果你   在服务上注册了一个监听器,或者在DOM上注册了一个监听器   没有删除的节点,你必须自己清理它   你冒着引入内存泄漏的风险。

     

最佳实践:指令应该自行清理。您可以   使用element.on('$ destroy',...)或范围。$ on('$ destroy',...)来运行a   删除指令时的清理功能。

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

在desstroy上,我猜你可以“取消注册”

elem.on('$destroy', function() {
        ctrl.$parsers.shift();
        ctrl.$formatters.shift()
        alert('ok');
      });