Angular JS $ compilation service causes memory leak in hours

I assume that due to misuse, the following code snippet creates a memory leak, I don’t understand why, I wrote a short screencast to demonstrate the problem, you can find it here: https://www.youtube.com/watch?v = IWCcOI5kN1c & feature = youtu.be

Here is the error code: it just dynamically compiles the html stored in the variable called content, but as soon as you start changing the variable contentthrough the text field, the code starts to create $ watch'es the situation gets worse as soon as you increase the number of bindings, since every Angular binding creates a new scope (which is obviously correct), but does not delete the old ones, and they are stored in 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>
+4
source share
1 answer

Well, sorry, I had no idea how bad a memory leak is! (This was my directive that Lu4 used)

, . . , DOM.

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 */ }
      });
    }
  }
});
+4

All Articles