引子:项目中遇到一个BUG,使用ui-bootstrap-tpls中的carousel插件设置轮播图时,改变ng-repeat中数组其中一个元素时,轮播图的顺序发生了变化。
代码如下:
<div carousel interval="app.Interval.value" no-pasue="false" no-transition="true">
<div slide ng-repeat="Item in advertiseList">
<div ng-style="{'background-color': Item.BgColor}">
<div class="slide">
<img class="aside cursor-pointer" ng-src="{{Item.ImgUrl|ImgUrl}}">
</div>
</div>
</div>
</div>function changeAd() {
angular.forEach($scope.advertiselList, function (item, index) {
if (item.Id == $scope.editAd.Id) {
$scope.advertiseList[index] = $scope.editAd;
}
});
}通过查看carsousel与slide组合指令的源码发现,当slide的DOM被重新渲染时,slide指令会在其销毁时先将其移除,然后渲染时push到尾部。在不想改变指令的情况下,这个问题只能从阻止ng-repeat对DOM的重新渲染着手。
正文:关于ng-repeat的重新渲染机制
如果ng-repeat数组的每个item都是一个对象,ng-repeat会为每个item都生成一个$$hashKey,用于绑定DOM。
解决方案:因此,这个问题可以简单的通过将ng-repeat="Item in advertiseList"改变为ng-repeat="Item in advertiseList track by $index(track by item.Id)"来解决。
在这种语法下,$$hashKey不起作用的,只要item.Id相等就会认为两个对象相同。
这里补充一下$watchCollection与$watch(obj,listener)、$watch(obj,listner,true)的区别
$watch(obj,listener)为引用比对,即只比对对象或数组的引用,对象中单个属性变化或者数组中元素的变化并不会触发listener函数,如果将对象或数组想象成树,那么$watch为只比对根节点。解决方案:针对浅层监听的机制,我们可以通过不改变数组中item引用的方法来骗过监听。
$watch(obj,listner,true)为值比对,$watch的第三个入参含义为是否值比对(或者全比对更好理解一点)即比对整个对象或数组所有的值,如果数组的元素是对象,会继续比对对象元素的所有属性,以此类推。可以理解成对象树的全遍历。
第一种方案策略太过简单,第二种策略又太消耗性能,所以诞生了一个折中的浅层监听策略。
$watchCollection是基于$watch监听的浅层监听函数,即对于数组来说,监听每个元素的引用,对于对象来说,监听每个属性的引用。可以理解为只遍历树的根节点和第一层元素。
$scope.advertiseList[index] = $scope.editAd;
前面这句代码只是简单的将$scope.advertiselList[index]的引用指向了一个新的地址,因此可以被检测到,可以将这句话改成
angular.copy($scope.editAd,$scope.advertiseList)
angular中的copy入参传入目标对象,会将源对象的所有属性一一赋值给目标对象,不会改变目标对象的引用。(详情请看另一篇angular中的深浅拷贝,如果“链接”点不动,说明还没写)。

744

被折叠的 条评论
为什么被折叠?



