【问题标题】:How do I force ngRepeat to not compare by reference?如何强制 ngRepeat 不通过引用进行比较?
【发布时间】:2018-05-25 23:17:10
【问题描述】:

在一个指令中,我使用了ngRepeat,它从作用域上的参数化函数中获取其项目。

以简化的方式,它看起来像这样:

<div data-ng-repeat="data in containerItems">
    <div data-ng-repeat="myItem in getItems(data) track by myItem.Id">
        <!-- some content -->
    </div>
</div>

我的问题是我似乎无法摆脱以下异常:

错误:[$rootScope:infdig] 达到 10 个 $digest() 迭代。中止!

据我根据其他几个问题(例如thisthis)得出的结论,ngRepeat 似乎认为每次重新请求这些项目时都会更新,导致它提示另一个消化循环,最终以太多连续而结束而放弃。

特别是用户g00fyone comment 对我很有见地:确实,我正在使用lodash_.map function 来生成ngRepeat 显示的项目列表,从而每个创建新的对象实例time ngRepeat 重新请求它的项目。

起初,我认为可以通过将项目生成函数的返回值保存到作用域上的变量中来解决这个问题。这消除了上述错误。

不幸的是,这对我来说不是一个合适的解决方案,因为范围由我的指令的几个实例共享,其中使用了ngRepeat。我真的需要每个ngRepeat 使用适当的参数从生成函数中检索项目。

一开始我是用的

track by $index

但我也尝试过类似的东西

track by myItem.Id

我曾希望这会强制ngRepeat 假设该项目仅在 ID 更改时才更改。然而,这一切似乎都无法阻止上述异常的发生。

有什么方法可以强制 AngularJS 仅在其 更改时才考虑 ngRepeat 中的项目而不是实例更改?

从结构上看,getItems(data) 的返回值似乎可以保存在data 上。但是,我正在处理的应用程序框架中的代码(这意味着它依赖于应用程序的许多模块的行为并且不能更改)将直接获取包含 data 的对象图并发送到服务器端后端。因此,data 不是计算运行时数据的正确位置。


为了提供更完整的示例,getItems 函数大致如下所示:

$scope.getItems = function (data) {
    var itemSet = itemSets[data.itemSetId];
    if (itemSet.customItems) {
        return itemSet.customItems.slice(1);
    } else {
        return _.map(standardItems.slice(1), function (si) {
            return {
                Id: si.code,
                Name: si.Description + ' ' + si.Owner
            };
        });
    }
};

这里的细节不相关。重要的部分是我根据参数data 中的某些内容从其他地方检索项目,然后这些项目可能采用需要转换为由我的ngRepeat-ed 模板处理的规范格式的格式。

【问题讨论】:

  • 可以添加你的函数getItems()吗?
  • 使用 ng-repeat 时,您确实应该使用不可变表。您链接的评论将首先按引用检查,然后按值检查。仅凭价值是无法做到的。
  • @DeblatonJean-Philippe:完成,添加了getItems 的示例。

标签: javascript angularjs angularjs-ng-repeat


【解决方案1】:

所以,我知道您有一个复杂的机制来获取您的数据,您需要在很多 ng-repeat 之间共享。

因为脏检查与 angularJS 一起工作的方式(简单 === 在保持值和 $scope 值之间),您必须将您的 ng-repeat 分配给单个引用(然后使用不可变对象,如果集合是更新)。

您是否有理由无法执行以下操作来获取数据?

myItems = {};
$scope.getItems = function (data) {
    if(!myItems[data.itemSetId]) {
        var itemSet = itemSets[data.itemSetId];
        if (itemSet.customItems) {
            myItems[data.itemSetId] = itemSet.customItems.slice(1);
        } else {
            myItems[data.itemSetId] _.map(standardItems.slice(1), function (si) {
                return {
                    Id: si.code,
                    Name: si.Description + ' ' + si.Owner
                };
            });
        }
    }
    return myItems[data.itemSetId];
};

【讨论】:

  • 这看起来很老套(为什么要缓存一些可以不费吹灰之力就可以计算出来的东西),但如果我的名义问题的直接答案是“你不能。”,看来我必须走这条路。稍后我会接受,以防万一出现另一个更好的解决方案。
  • @O.R.Mapper 我希望你在接受我的回答之前先尝试::
  • @O.R.Mapper 您的组件对于演示组件来说也可能太大了。您可能可以将代码拆分为子组件以提高清晰度。
  • 组件是别人写的,在应用的各个地方都用到了。我正在仔细尝试在不破坏其他部分的情况下编织一个新功能,因此大规模的重组可能是不可行的。话虽如此,我在原始示例中缩短为 &lt;!-- some content --&gt; 的部分 已经只是调用了我们的其他一些指令。
  • 至于::,这似乎不起作用:getItems 的结果会在data 中的某些内容发生变化时发生变化,如果我写::getItems(data)ngRepeat 会发生变化一旦data 中的某些内容发生了变化,就不再重新评估getItems
【解决方案2】:
  1. 如果getItems(data)永远不会改变,你可以使用一次性绑定:ng-repeat="a in ::getItems(data)"
  2. 如果它发生变化,您应该将它存储在 $scope 中并手动更新它——这可能是最好的方法。在 ng-repeat 中使用方法是不好的风格,很可能会在以后导致性能问题。所以,这看起来像:
<div data-ng-repeat="myItem in myItems track by myItem.Id">
$scope.myItems = getItems(data);
//somewhere you need to update myItems -- can be not easy
  1. 您的问题是您的 getItems 不应该创建对象,因此您可以通过缓存对象或完整结果来修复它(请参阅 Deblaton 答案),但这通常不好。所以,这看起来像:

    <div data-ng-repeat="myItem in getItems(data) track by myItem.Id">
    
    $scope.objectCache = {};
    $scope.getItems = function (data) {
        var itemSet = itemSets[data.itemSetId];
        if (itemSet.customItems) {
            return itemSet.customItems.slice(1);
        } else {
            return _.map(standardItems.slice(1), function (si) {
                if (!objectCache[si.code]) { 
                    objectCache[si.code] = {
                       Id: si.code,
                       Name: si.Description + ' ' + si.Owner
                    }; 
                }
                return objectCache[si.code];
            });
        }
    };
    

【讨论】:

  • 等等,2(“可能是最好的方法”)与 3(“一般来说不好”)有何不同?你的意思是我不应该使用以不同格式提供数据的数据源?嗯,就是这样。我无法控制来自其他组件的数据是如何形成的。
  • 一次性绑定是最好的选择(比我的好)。但是,关于 OP 需要有一个方法来获取其数据,3 优于 2 ;-)
  • “你需要更新 myItems 的地方——这并不容易”——有点,是的。基本上,我需要在&lt;div data-ng-repeat="myItem in myItems track by myItem.Id"&gt; 的两次执行之间更新myItems,这将使用不同的myItems,并且彼此相邻显示。
  • @O.R.Mapper 不,如果你有 2 个不同的 getItems(1), getItems(2) -> 你应该有 myItems1, myItems2 在范围内。我的意思是其他变化
猜你喜欢
  • 2014-11-04
  • 2014-02-19
  • 2020-06-11
  • 2012-06-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-04-30
  • 1970-01-01
相关资源
最近更新 更多