Angular JS $compile service causes $watch memory leak

 ̄綄美尐妖づ 提交于 2019-12-01 05:55:00

问题


I guess due to misuse the following piece of code creates a memory leak, I can't understand why, I've recorded short screencast to demonstrate the problem, you can find it here: https://www.youtube.com/watch?v=IWCcOI5kN1c&feature=youtu.be

Here is the failing code, what it does is it just dynamically compiles the html stored in variable called content, but as soon as you start changing content variable via the textarea, the code starts creating $watch'es the situations gets even worser as soon as you increase the amount of bindings, for each binding Angular creates new scope (which is obviously right) but it doesn't remove the old ones and they are kept in the memory

<!DOCTYPE html>
<html ng-app="plunker">

  <head>
    <meta charset="utf-8" />
    <title>AngularJS Plunker</title>
    <script>document.write('<base href="' + document.location + '" />');</script>
    <script data-require="angular.js@1.3.x" src="https://code.angularjs.org/1.3.15/angular.js" data-semver="1.3.15"></script>
      <script>
          var app = angular.module('plunker', []);

          app.controller('MainCtrl', function($scope) {
              $scope.name = 'World';
          });

          app.directive('compile', function($compile) {
              return {
                  restrict: 'A',
                  link: function(scope, elem, attrs) {
                      scope.$watch(attrs.compile, function(newVal) {
                          // create a span (an inline element) so we have an actual DOM node to
                          // set the innerHTML of.
                          var newElem = document.createElement('span');

                          newElem.innerHTML = newVal;

                          // clear out the contents of this element
                          elem[0].innerHTML = '';

                          // and replace it with the raw (uncompiled) node
                          elem[0].appendChild(newElem);

                          // now the node is in the DOM so we can compile it
                          // but we want to use a try..catch because the user
                          // might be in the middle of typing a new expression,
                          // but the syntax right now is not valid so the
                          // expression parser will throw an error.
                          try {
                              // compile the node in the DOM with the existing scope
                              $compile(newElem)(scope);
                          } catch(e) { /* don't need to do anything here */
                          }
                      });
                  }
              };
          });
      </script>
  </head>

  <body ng-controller="MainCtrl" ng-init="variable = 3; content = '{{ variable }}'">
    <div>
      The value of $scope.variable === "{{ variable }}"
    </div>
    <div>
      The value of $scope.content === "{{ content }}"
    </div>
    <br>
    <div>
    The value of $scope.content is <b>ng-model</b>'ed via the following textarea:<br>
    </div>

    <textarea rows="3" ng-model="content"></textarea>

    <div style="border: 1px solid black">
      Instead of rendering the value of the $scope.content field which is currently equal to "{{ content }}" I need to render compiled and evaluated value which should be equal to "{{ variable }}"
    </div>

    <hr>

    <p>Look! It works: <span compile="content"></span></p>
  </body>

</html>

回答1:


Okay, I apologise, I had no idea how bad the memory leak was! (It was my directive Lu4 was using)

I believe I have now got rid of the leak and cleaned up a lot better. I created a new child scope each time I compiled anew and destroyed the old one first. And as per this excellent tip I made sure to destroy the scope before clearing out the DOM element.

Updated Plunkr

app.directive('compile', function($compile) {
  return {
    restrict: 'A',
    link: function(scope, elem, attrs) {
      var prevScope;
      scope.$watch(attrs.compile, function(newVal, oldVal) {
        // create a span (an inline element) so we have an actual DOM node to
        // set the innerHTML of.
        var newElem = document.createElement('span');
        newElem.innerHTML = newVal;
        // clean up first
        if (prevScope) {
          prevScope.$destroy();
          prevScope = null;
        }
        // clear out the contents of this element
        elem.empty();
        // and replace it with the raw (uncompiled) node
        elem[0].appendChild(newElem);
        // now the node is in the DOM so we can compile it
        // but we want to use a try..catch because the user
        // might be in the middle of typing a new expression,
        // but the syntax right now is not valid so the
        // expression parser will throw an error.
        try {
          // compile the node in the DOM with a child of the existing scope
          prevScope = scope.$new();
          $compile(newElem)(prevScope);
        } catch (e) { /* don't need to do anything here */ }
      });
    }
  }
});


来源:https://stackoverflow.com/questions/29190471/angular-js-compile-service-causes-watch-memory-leak

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!