角度滤波作品,但会导致“10 $消化达到循环”(Angular filter works but c

2019-09-02 17:48发布

我从结构像这样我的后端服务器接收数据:

{
  name : "Mc Feast",
  owner :  "Mc Donalds"
}, 
{
  name : "Royale with cheese",
  owner :  "Mc Donalds"
}, 
{
  name : "Whopper",
  owner :  "Burger King"
}

对于我的看法,我想“反转”的名单。 即我想列出每一位业主,并为所有者列表中的所有汉堡包。 我可以通过使用underscorejs功能实现这一groupBy在过滤器中,然后我在与使用ng-repeat指令:

JS:

app.filter("ownerGrouping", function() {
  return function(collection) {
    return _.groupBy(collection, function(item) {
      return item.owner;
    });
  }
 });

HTML:

<li ng-repeat="(owner, hamburgerList) in hamburgers | ownerGrouping">
  {{owner}}:  
  <ul>
    <li ng-repeat="burger in hamburgerList | orderBy : 'name'">{{burger.name}}</li>
  </ul>
</li>

这工作正常,但我得到一个巨大的错误堆栈跟踪时,列表与错误消息“10 $消化达到循环”渲染。 我有一个很难看到我的代码是如何创建并通过该消息暗示一个无限循环。 是否有任何人知道为什么吗?

这里是一个普拉克链接的代码: http://plnkr.co/edit/8kbVuWhOMlMojp0E5Qbs?p=preview

Answer 1:

这是因为_.groupBy返回每次运行时对象的集合。 角的ngRepeat没有意识到,这些对象是相同的,因为ngRepeat身份对其进行跟踪。 新对象会导致新的身份。 这使得角度认为自从上次检查,这意味着角应该运行另一个检查(又称为消化),事情有了变化。 接下来摘要最终获得又一组新的对象,所以另一摘要被触发。 直到棱角分明重复放弃。

一个简单的方法来摆脱错误的是,以确保您的过滤器返回每次(当然,除非它已经改变)对象的同一集合。 您可以通过使用下划线做到这一点很容易_.memoize 。 只是包装在memoize的过滤函数:

app.filter("ownerGrouping", function() {
  return _.memoize(function(collection, field) {
    return _.groupBy(collection, function(item) {
      return item.owner;
    });
  }, function resolver(collection, field) {
    return collection.length + field;
  })
});

如果您打算为您的过滤器使用不同的域值的解析器功能是必需的。 在上面的例子中,使用该阵列的长度。 更好的是集合减少到独特的MD5哈希值的字符串。

见这里plunker叉 。 memoize的会记住特定输入的结果,并返回相同的对象,如果输入的是和以前一样。 如果值经常变化,虽然那么你应该检查是否_.memoize丢弃旧的结果,以避免随着时间的推移内存泄漏。

调查远一点我看到ngRepeat支持扩展语法... track by EXPRESSION ,这可能是有益的不知何故,允许你告诉角度看owner的餐馆,而不是对象的身份。 这将是对记忆化替代上述欺骗,虽然我无法管理,以测试它在plunker(从之前可能是老版的角track by被执行?)。



Answer 2:

好吧,我想我想通了。 通过采取一看开始对ngRepeat源代码 。 通知线路199:这是我们建立手表阵列/对象上我们重复结束,因此,如果它或它的元素改变摘要周期将被触发。

$scope.$watchCollection(rhs, function ngRepeatAction(collection){

现在,我们需要找到的定义$watchCollection ,它开始于360线rootScope.js 。 这个函数是在我们的数组或对象的表达,这在我们的情况下是通过hamburgers | ownerGrouping hamburgers | ownerGrouping 。 上线365字符串表达式变成使用功能$parse服务,该服务将在以后调用的函数,而每到这个观察家运行时间:

var objGetter = $parse(obj);

这种新的功能,这将评估我们的过滤器,并得到结果数组,被调用短短的几行下:

newValue = objGetter(self);

所以newValue认为我们的过滤数据的结果,已应用于GROUPBY后。

接着向下滚动到线408和一起来看看下面的代码:

        // copy the items to oldValue and look for changes.
        for (var i = 0; i < newLength; i++) {
          if (oldValue[i] !== newValue[i]) {
            changeDetected++;
            oldValue[i] = newValue[i];
          }
        }

第一次运行时,属性oldValue只是一个空阵列(上面设置为“internalArray”),这样的变化被检测到。 然而,它的每个元素将被设置为NEWVALUE的相应元素,让我们期待它运行一切,下一次应该匹配,并没有改变被检测到。 所以,当一切工作正常此代码将被运行两次。 一旦安装,检测从最初的空状态的变化,然后再次,由于检测到的变化迫使一个新的周期消化运行。 在正常情况下不会发生变这第二次运行过程中,因为在这一点上被检测到, (oldValue[i] !== newValue[i])将是错误的对所有的i。 这就是为什么你在你的工作例如看到2个的console.log输出。

但是,在发生故障的情况下,您的过滤器的代码生成与每一个它的运行时间的新elments一个新的数组 。 虽然这个新的数组的elments具有相同的值作为旧数组中的元素(这是一个完美的副本),它们是不一样的实际元素 。 也就是说,他们指的是存储不同的对象只是碰巧有相同的属性和值。 因此,在你的情况oldValue[i] !== newValue[i]永远是真实的,出于同样的原因是,例如, {x: 1} !== {x: 1}总是正确的。 而改变总是会被检测到。

所以,重要的问题是,你的过滤器正在创造每一个它的运行时间数组的一个新副本 ,包括那些原来阵列的elments副本新元素 。 因此,通过ngRepeat观察者设置正好卡在本质上是一个无限递归循环,总是检测的变化,引发了新的消化周期。

下面是再现了同样的问题,你的代码的简化版本: http://plnkr.co/edit/KiU4v4V0iXmdOKesgy7t?p=preview

如果过滤器停止创建每次的运行时间的新数组的问题消失。



Answer 3:

新来AngularJS 1.2是一个“轨道由”选项为NG-重复指令。 你可以用它来帮助角度认识到,不同对象实例确实应该被认为是相同的对象。

ng-repeat="student in students track by student.id"

这将有助于unconfuse角在你们这样的情况下你用下划线做重量级切片和切块,其中,生产而不是仅仅过滤他们的新对象。



Answer 4:

感谢您的memoize的解决方案,它工作正常。

然而, _.memoize使用第一个传递的参数作为缓存默认密钥。 这可能不是很方便,特别是如果第一个参数将始终是相同的。 我们希望,这种行为是通过配置的resolver参数。

在下面的例子中,第一参数将始终是相同的阵列,和其上场应该通过进行分组,第二个表示字符串:

return _.memoize(function(collection, field) {
    return _.groupBy(collection, field);
}, function resolver(collection, field) {
    return collection.length + field;
});


Answer 5:

请原谅简洁,但尝试ng-init="thing = (array | fn:arg)"和使用thing在你的ng-repeat 。 对我的作品,但这是一个广泛的问题。



Answer 6:

我不知道为什么这个错误是未来,但在逻辑上过滤函数被调用为数组的每个元素。

在你的情况,你所创建的过滤函数返回时,阵列被更新,而不是对数组的每个元素应该只被调用的函数。 该函数返回的结果然后可以一定到HTML。

我已付出了plunker,并创建了自己的执行在这里http://plnkr.co/edit/KTlTfFyVUhWVCtX6igsn

它不使用任何过滤器。 其基本思想是调用groupBy开始和无论何时添加的元素

$scope.ownerHamburgers=_.groupBy(hamburgers, function(item) {
      return item.owner;
    });



$scope.addBurger = function() {
    hamburgers.push({
      name : "Mc Fish",
      owner :"Mc Donalds"
    });
    $scope.ownerHamburgers=_.groupBy(hamburgers, function(item) {
      return item.owner;
    });
  }


Answer 7:

对于它的价值,多加一个例子和解决方案,我做了一个简单的过滤器是这样的:

.filter('paragraphs', function () {
    return function (text) {
        return text.split(/\n\n/g);
    }
})

有:

<p ng-repeat="p in (description | paragraphs)">{{ p }}</p>

这造成了所描述的无限递归$digest 。 很容易固定:

<p ng-repeat="(i, p) in (description | paragraphs) track by i">{{ p }}</p>

这也是必要的,因为ngRepeat矛盾不喜欢中继器,即"foo\n\nfoo"会导致因为两个相同的段落的错误。 如果段落的内容,实际上改变这种解决方案可能不适合,它是重要的是他们继续得到消化,但对我来说这不是一个问题。



文章来源: Angular filter works but causes “10 $digest iterations reached”