ng-repeat doesn't work when HTML is altered un

2019-07-26 03:08发布

问题:

Directive code:

.directive('replace', function($compile) {
      return function (scope, element) {
        element.html(element.html().replace("Hej", "Hey!"));
        $compile(element.contents())(scope);
      }
  });
})

HTML

 <div ng-controller="GreeterController">
     <div replace>Hej <div ng-repeat="e in arr">{{ e }}</div>
     </div>
 </div>

Controller

app.controller('GreeterController', ['$scope', function($scope) {
    $scope.arr = [1, 2, 3, 4];
}]);

Live example

As the title says, ng-repeat doesn't work when I'm using the directive from above on HTML which contains it. But once I remove that line which uses .replace() command to replace part of HTML then ng-repeat starts working for some reason.
Does anyone know where's the actual problem? I have tried everything and I still seem to not get it why it doesn't work as it should.

回答1:

The manipulation can also be done in the compile phase:

app.directive("replace", function(){
    return {
        compile: function(element){
            element.html(element.html().replace('Hej', 'Hey!'));
            /*return {
                pre: function(scope, element){
                  element.html(element.html().replace('Hej', 'Hey!'));
                }
            }*/
        }
    };
});

The original problem was caused because the linking of the ng-repeat directive was done before the element with that directive is replaced with the replace operation. The watchers associated with the ng-repeat directive then operate on elements that are no longer attached to the visible DOM.

By moving the replace operation to either the compile phase or the preLink phase, the replacing of the element that has the ng-repeat directive is done before the ng-repeat directive is linked. The watchers associated with ng-repeat directive then work with the replaced DOM.



回答2:

You should let Angular and its change detection cycle do the HTML manipulation for you, instead of directly changing it yourself.

I've edited your example to use scope bindings to achieve what you wanted:

<!doctype html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Example - example-compile-production</title>
  

  <script src="//code.angularjs.org/snapshot/angular.min.js"></script>
  

  
</head>
<body ng-app="compileExample">
  <script>
  angular.module('compileExample', [], function($compileProvider) {
    // configure new 'compile' directive by passing a directive
    // factory function. The factory function injects the '$compile'
    $compileProvider.directive('replace', function($compile) {
        return {
            link: function (scope, element) {
              scope.greeting = 'Hey!';
              $compile(element.contents())(scope);
            }
        }
    });
  })
  .controller('GreeterController', ['$scope', function($scope) {
    $scope.test = [1, 2, 3, 4];
    $scope.greeting = 'Hej';
  }]);
</script>
<div ng-controller="GreeterController">
  <div replace>{{greeting}} <div ng-repeat="e in test">{{ e }}</div></div>
</div>
</body>
</html>

<!-- 
Copyright 2017 Google Inc. All Rights Reserved.
Use of this source code is governed by an MIT-style license that
can be found in the LICENSE file at http://angular.io/license
-->

Note: I removed "scope: false" as that is the default behaviour.

EDIT: Since you must replace HTML in place here's a solution with jQuery:

<!doctype html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Example - example-compile-production</title>
  

  <script src="//code.angularjs.org/snapshot/angular.min.js"></script>
  <script
  src="https://code.jquery.com/jquery-3.1.1.min.js"
  integrity="sha256-hVVnYaiADRTO2PzUGmuLJr8BLUSjGIZsDYGmIJLv2b8="
  crossorigin="anonymous"></script>

  
</head>
<body ng-app="compileExample">
  <script>
  angular.module('compileExample', [], function($compileProvider) {
    // configure new 'compile' directive by passing a directive
    // factory function. The factory function injects the '$compile'
    $compileProvider.directive('replace', function($compile) {
        return function (scope, element) {
          $(element).find( ".translation" ).replaceWith("Hey!");
        }
    });
  })
  .controller('GreeterController', ['$scope', function($scope) {
    $scope.arr = [1, 2, 3, 4];
  }]);
</script>
<div ng-controller="GreeterController">
  <div replace><span class="translation">Hej</span> <div ng-repeat="e in arr">{{ e }}</div></div>
</div>
</body>
</html>

<!-- 
Copyright 2017 Google Inc. All Rights Reserved.
Use of this source code is governed by an MIT-style license that
can be found in the LICENSE file at http://angular.io/license
-->



回答3:

Solved it like this:

.directive("replace", function(){
    return {
        compile: function(){
            return {
                pre: function(scope, element){
                    element.html(element.html().replace('Hej', 'Hey!'));
                }
            }
        }
    };
});

Live example