当对象键保持不变时,AngularJS ng-repeat更新不适用?

时间:2017-03-28 14:47:01

标签: angularjs angularjs-ng-repeat

我正在尝试制作一个最小但很花哨的AngularJS教程示例,我遇到了一个问题,在更新模型的整个树之后(在ng-change update的范围内),一个驱动的模板顶级ng-repeat根本不会重新渲染。

但是,如果我在战略位置添加代码$scope.data = {},它就会开始工作;但随后显示屏闪烁而不是光滑。并且它不是AngularJS自动数据绑定如何工作的一个很好的例子。

我缺少什么;什么是正确的解决方案?

确切代码 - 从下拉列表中选择一个国家/地区 - 这个jsFiddle不起作用:http://jsfiddle.net/f9zxt36g/ 这个jsFiddle工作但闪烁:http://jsfiddle.net/y090my10/

var app = angular.module('factbook', []);
app.controller('loadfact', function($scope, $http) {
  $scope.country = 'europe/uk';
  $scope.safe = function safe(name) { // Makes a safe CSS class name
    return name.replace(/[_\W]+/g, '_').toLowerCase();
  };
  $scope.trunc = function trunc(text) { // Truncates text to 500 chars
    return (text.length < 500) ? text : text.substr(0, 500) + "...";
  };
  $scope.update = function() { // Handles country selection
    // $scope.data = {}; // uncomment to force rednering; an angular bug?
    $http.get('https://rawgit.com/opendatajson/factbook.json/master/' +
        $scope.country + '.json').then(function(response) {
      $scope.data = response.data;
    });
  };
  $scope.countries = [
    {id: 'europe/uk', name: 'UK'},
    {id: 'africa/eg', name: 'Egypt'},
    {id: 'east-n-southeast-asia/ch', name: 'China'}
  ];
  $scope.update();
});

模板由ng-repeat驱动:

<div ng-app="factbook" ng-controller="loadfact">
  <select ng-model="country" ng-change="update()"
      ng-options="item.id as item.name for item in countries">
  </select>
  <div ng-repeat="(heading, section) in data"
       ng-init="depth = 1"
       ng-include="'recurse.template'"></div>
  <!-- A template for nested sections with heading and body parts -->
  <script type="text/ng-template" id="recurse.template">
    <div ng-if="section.text"
         class="level{{depth}} section fact ng-class:safe(heading);">
      <div class="level{{depth}} heading factname">{{heading}}</div>
      <div class="level{{depth}} body factvalue">{{trunc(section.text)}}</div>
    </div>
    <div ng-if="!section.text"
         class="level{{depth}} section ng-class:safe(heading);">
      <div class="level{{depth}} heading">{{heading}}</div>
      <div ng-repeat="(heading, body) in section"
           ng-init="depth = depth+1; section = body;"
           ng-include="'recurse.template'"
           class="level{{depth-1}} body"></div>
    </div>
  </script>
</div>

我错过了什么?

3 个答案:

答案 0 :(得分:1)

您通过在section指令section = body;内执行ng-if来更改$scope属性的引用。细节上发生了什么(https://docs.angularjs.org/api/ng/directive/ngIf):

    {li} ng-repeat data$scope创建了ng-repeat,其中包含headingsection;
  1. 来自ng-include $compile'的模板,第一步来自$scope;
  2. 根据文档ng-if使用继承创建自己的$scope并重复headingsection;
  3. 在执行ng-repeat的模板中
  4. section = body并更改了对section内的ngIf.$scope属性的引用;
  5. 由于section是继承的属性,因此您定向的是显示来自其他section的{​​{1}}属性,与$scope的父级的初始$scope不同。
  6. This is easily traced - 只需添加:

    ngIf

    并且您会注意到... <script type="text/ng-template" id="recurse.template"> {{section.Background.text}} ... 实际上指定了正确的值并相应地进行了更改,而section.Background.text下的section.text并未改变。

    无论您更新ngIf.$scope引用,$scope.data都不关心,因为它自己的ng-if仍然引用了垃圾收集器未清除的上一个对象。

    <强> Reccomdendation: 不要在模板中使用递归。序列化您的响应并创建将在不需要递归的情况下显示的平面对象。您希望模板显示静态标题和动态文本。这就是为什么你有落后的渲染 - 你没有使用单向绑定来处理诸如节标题之类的静态内容。 Some performance tips

    P.S。在管理数据时,不要在模板中进行递归,而是在业务逻辑位置进行递归。 ECMAScript对引用非常敏感,最佳实践是保持模板简单 - 模板中没有赋值,没有变异,没有业务逻辑。当你在section更新你的每一个$watcher这么多次没有结束时,Angular也会与section一起狂野。

答案 1 :(得分:0)

感谢Apperion和anoop的分析。我已经缩小了问题的范围,结果是ng-repeat和ng-init之间似乎存在错误的交互,这会阻止在ng-init中复制重复变量时应用更新。这是一个最小化的示例,它显示了不使用任何递归或包含或阴影的问题。 https://jsfiddle.net/7sqk02m6/

<div ng-app="app" ng-controller="c">
  <select ng-model="choice" ng-change="update()">
    <option value="">Choose X or Y</option>
    <option value="X">X</option>
    <option value="Y">Y</option>
  </select>
  <div ng-repeat="(key, val) in data" ng-init="copy = val">
    <span>{{key}}:</span> <span>val is {{val}}</span>  <span>copy is {{copy}}</span>
  </div>
</div>

控制器代码只是在&#34; X&#34;之间切换数据。和&#34; Y&#34;和空版本:

var app = angular.module('app', []);
app.controller('c', function($scope) {
  $scope.choice = '';
  $scope.update = function() {
    $scope.data = {
      X: { first: 'X1', second: 'X2' },
      Y: { first: 'Y1', second: 'Y2' },
      "": {}
    }[$scope.choice];
  };
  $scope.update();
});

请注意{{copy}}{{val}}在循环中的行为应该相同,因为copy只是val的副本。它们只是'X1'之类的字符串。事实上,第一次选择&#39; X&#39;时,效果很好 - 制作副本,它们遵循循环变量并通过循环更改值。 val和副本是相同的。

first: val is X1 copy is X1
second: val is X2 copy is X2

但是当你更新到&#39; Y&#39;数据的版本,{{val}}变量更新为Y版本,但{{copy}}值不更新:它们保留为X版本。

first: val is Y1 copy is X1
second: val is Y2 copy is X2

同样,如果您清除所有内容并以“&#39; Y&#39;”开头,则更新为“X&#39;”,副本会被卡在Y版本中。

结果是:ng-init似乎无法在这种情况下复制循环变量时以某种方式正确设置观察者。我不能很好地遵循Angular内部,以了解bug的位置。但是避免ng-init解决问题。原始示例的一个版本在没有闪烁的情况下效果很好:http://jsfiddle.net/cjtuyw5q/

答案 2 :(得分:0)

如果您想控制ng-repeat正在跟踪哪些键,您可以使用trackby语句:https://docs.angularjs.org/api/ng/directive/ngRepeat

<div ng-repeat="model in collection track by model.id">
  {{model.name}}
</div>

修改其他属性不会触发刷新,这可能对性能非常有利,或者如果您对对象的所有属性进行搜索/过滤,则会很痛苦。