将 ng-repeat 与指令一起使用会导致手表出现缺陷

Using ng-repeat with directive causes flaw in watch

我制定了一个新指令,它基本上是一个扩展 angular UI 轮播 bootstrap 的新轮播。这个新的轮播将在一帧中显示多个 div。我的新指令接受数组中的任何数据和每个数据的自定义 html 模板。

但是,如果我将轮播与指令一起使用,我会在指令中的 watch 看到奇怪的行为。一切正常,但我的指令中的手表总是为 newValoldVal 获取相同的值。我的意思是,这是我的轮播代码:

<slide ng-repeat="frame in ctrl.frames">
     <div ng-repeat="divs in frame.divs">
       <custom-directive data="divs"></custom-directive>
     </div>
</slide>

在我的 customDirective 控制器中,我观察到这样的数据变化:

$scope.$watch("ctrl.data", function(newVal, oldVal){
     if (newVal !== oldVal) {
         // data is updated, redraw the directive in my case
         // however newVal is always the same as oldVal
     }
})

newVal 和 oldVal 始终相同。我希望初始状态为 oldVal = undefined,而 newVal 将是我的新数据。然而,事实并非如此。数据作为双向绑定传递给传送带和自定义指令(在每个指令的范围内使用“=”运算符)。

为什么会这样?我对此进行了长期调查,以下是我的发现:

  1. 如果我不在我的旋转木马中使用 ng-repeat,这个可以工作oldVal 将是 undefined 并且 newVal 将是我在初始状态下的数据。但是为什么 ng-repeat 会导致这个呢?我已经阅读了很多关于 原型继承的黄金法则 的文章,其中说 ng-repeat 将创建新的 childScope hides/shadows parent ,但这只发生在 原始对象 上,我正在将 array 传递给我的数据。

我需要在我的轮播指令中使用 ng-repeat。所以我需要知道为什么 ng-repeat 会导致这个。有什么建议吗?

更新: 在 Plunkr here 中重现了该问题。如您所见,oldValue 始终与 newValue 相同(我预计 oldValue 一开始是未定义的)

我认为您遇到的问题只是对 $watch 工作原理的误解。

$watch 预期 以相等的值初始化。请参阅文档 here。具体来说:

After a watcher is registered with the scope, the listener fn is called asynchronously (via $evalAsync) to initialize the watcher. In rare cases, this is undesirable because the listener is called when the result of watchExpression didn't change. To detect this scenario within the listener fn, you can compare the newVal and oldVal. If these two values are identical (===) then the listener was called due to initialization

所以换句话说,你检查它们是否相等是为了让你检测到初始调用

在你提供的Plunker中,如果你需要做一些初始化代码,你可以做两件事:

  1. 你可以在 $watch 函数中检查它们 是否 相等,如果它们相等,那么这是初始调用
  2. 或者,在 link 函数中的那个函数之外,这些值是它们在那里的初始值(因为 link 函数等同于 post-link,这意味着 scope 值已经链接)所以你可以把你的代码放在那里

分叉你的 Plunker here。请注意,我将 alert 移到了 $watch 之外,该值仍然有效

编辑:

当它不在 ng-repeat 中并且它的设置类似于您在 Plunkr 中注释掉的代码时,您看到差异的原因是您在 $timeout 中添加了数据。当页面 最初 加载时,以下是两种类型呈现的内容:

  1. <a1 prop="data[0]"></a1>
    • HTML 看起来和写的一样。 data=[]。指令元素存在,调用 linkdata[0]=undefined$watch 调用 prop=undefined
  2. <!-- ngRepeat: element in data track by $index -->
    • HTML 只是一个评论。等待 data 被填充。不存在指令元素,这意味着 link 未被调用

当您将项目添加到 data 超时后,它们看起来像这样:

  1. <a1 prop="data[0]"></a1>
    • 同上。 data[0] 现在已定义,因此 prop 已定义
  2. <div ng-repeat="element in data track by $index" class="ng-scope"> <a1 prop="element" class="ng-isolate-scope"></a1> </div> (x3)
    • 页面现在有指令元素。在每个 data 上调用 link 函数。 $watchprop 个链接的值调用

当您在 link 函数中注册 $watch 时,Angular 已经在 preLink 阶段处理了绑定,因此您永远不会看到 undefined 你的观察者第一次被执行时(初始化调用是 oldVal 和 newVal 可能相同的唯一时刻。如果观察者在 绑定解析之前 注册,则 oldValue 将是 undefined)

如果你真的想看它,你可以覆盖compile阶段并添加自定义preLink方法(默认linkpostLink).

但我真的怀疑你是否想这样做。为什么第一次没有 undefined 是个问题?您应该尝试解释您面临的真正问题。

此外,请注意,如果传递给指令的 divs 是数组,则应使用 scope.$watchColleciton 而不是 scope.$watch 来检测数组元素的变化整个数组指针的变化。