【问题标题】:AngularJS ng-repeat update does not apply when object keys stay the same?当对象键保持不变时,AngularJS ng-repeat 更新不适用?
【发布时间】:2017-03-29 07:37:03
【问题描述】:

我正在尝试制作一个最小但花哨的 AngularJS 教程示例,我遇到了一个问题,即在为模型更新整个树后(在 ng-change 更新的范围内),驱动的模板顶级 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>

我错过了什么?

【问题讨论】:

  • 这应该是ng-include 的问题,因为它创建了新的范围。如果您只是渲染您的json 数据&lt;select ng-model="country" ng-change="update()" ng-options="item.id as item.name for item in countries"&gt; &lt;/select&gt;{{data}},您会发现它运行顺利。我建议使用原型链模型(如$scope.data.data)来保留模板内的范围。
  • 感谢 anoop 和 Aperion。我仍然不确定是什么导致了问题,但我找到了一个不需要我消除递归或使用ng-include 的修复程序。看起来这是ng-initng-repeat 之间的不良交互。如果我按如下方式更改它,jsfiddle.net/fL951g83 以消除在 ng-repeat 中的循环变量上使用 ng-init,它可以正常工作。会不会和stackoverflow.com/questions/15355122/…有关?

标签: angularjs angularjs-ng-repeat


【解决方案1】:

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

  1. ng-repeatdata 上为 ng-repeat 创建了 $scope,具有 headingsection 属性;
  2. 模板来自 ng-include $compile'd 和 $scope 从第一步开始;
  3. 根据文档ng-if 使用继承创建了自己的$scope 并复制了headingsection
  4. 模板内部的ng-repeat 执行section = body 并更改了指向section 内部ngIf.$scope 属性的引用;
  5. 由于section 是继承属性,您指示显示来自另一个$scopesection 属性,不同于ngIf 父级的初始$scope

This is easily traced - 只需添加:

...
<script type="text/ng-template" id="recurse.template">
  {{section.Background.text}}
...

您会注意到section.Background.text 实际上指定了适当的值并相应地更改,而ngIf.$scope 下的section.text 从未更改过。

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

推荐: 不要在模板中使用递归。序列化您的响应并创建无需递归即可显示的平面对象。由于您的模板希望显示静态标题和动态文本。这就是为什么你有滞后渲染的原因——你没有对像节标题这样的静态事物使用单向绑定。 Some performance tips.

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

【讨论】:

  • 感谢您的分析。但是为什么当$scope.data = {} 优先时它会起作用呢?如果陈旧的参考文献在附近徘徊,那么它们不应该也在那里徘徊吗?递归的哪一部分不起作用?事情看起来像是被设计用于递归,例如,ng-repeat 也设置了 $scope 的副本,所以它不应该是 ng-if 的 section 而是 ng-repeat 的 section 正在发生变化. depth 也会发生同样的事情,它可以正常工作。
  • 不要忘记深度不是对对象的引用,而是原始值。我建议您深入了解 JavaScript 中的对象引用以更好地理解
  • 当您将数据属性分配给新的空对象时,ngRepeat 清除自己的范围,因为数据没有键,模板被删除,ngIf 被删除并按照文档中的描述清除自己的范围。从家长的角度思考。如果您有 ngShow 例如在隐藏时不会被清除,则在分配新对象后部分将是相同的。
  • 一旦您将每个 ngIf 中的部分指向与父部分不同的新引用,就会出现问题。您的主要问题:符号“=”在您的情况下真正做了什么?不 - 它没有更新节值,它正在将名为节的引用更改为不同的对象。
【解决方案2】:

感谢 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>

控制器代码只是在“X”和“Y”和空版本之间切换数据:

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' 这样的字符串。事实上,当您第一次选择“X”时,它的效果很好 - 复制了副本,它们跟随循环变量并通过循环更改值。 val和copy是一样的。

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

但是当您更新到数据的“Y”版本时,{{val}} 变量会更新到 Y 版本,但 {{copy}} 值不会更新:它们保持为 X 版本。

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

同样,如果您清除所有内容并从“Y”开始,然后更新为“X”,则副本会卡在 Y 版本。

结果是:ng-init 在这种情况下复制循环变量时似乎无法正确设置观察者。我无法很好地遵循 Angular 内部结构来理解错误在哪里。但是避免ng-init 可以解决问题。这里有一个运行良好且无闪烁的原始示例版本:http://jsfiddle.net/cjtuyw5q/

【讨论】:

    【解决方案3】:

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

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

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

    【讨论】:

      猜你喜欢
      • 2013-05-05
      • 2019-04-30
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多