【问题标题】:Using ng-repeat with directive causes flaw in watch使用带有指令的 ng-repeat 会导致手表出现缺陷
【发布时间】:2026-01-14 23:15:03
【问题描述】:

我制定了一个新指令,它基本上是一个新的轮播,它扩展了 Angular UI 轮播引导程序。这个新的轮播将在一帧中显示多个 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 = undefinednewVal 将是我的新数据。然而,情况并非如此。数据作为双向绑定传递给 carousel 和自定义指令(在每个指令的范围内使用 '=' 运算符)。

为什么会发生这种情况?我已经调查了很长时间,以下是我的发现:

  1. 如果我不使用 ng-repeat 在我的轮播中,这会起作用oldVal 将是 undefinednewVal 将是我在初始状态下的数据。但是为什么 ng-repeat 会导致这种情况呢?我读过很多关于原型继承的黄金法则的文章,说ng-repeat会创建新的childScope来隐藏/隐藏父级,但这只会发生在原始对象,我正在将一个数组传递给我的数据。

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

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

【问题讨论】:

  • 你确定这个函数真的被调用了吗?还是只是它从未进入您的if (newVal !== oldVal) 代码? (最初除外)
  • 此外,还不清楚ctrl.data 与html 中ng-repeats 中的数据有何关系。 ctrl.data 到底在哪里使用?这可能有助于找出任何范围/结构问题
  • @brettvd 是的,这个函数正在被调用。我很确定,因为当我调试时,正在调用 watch 但 newVal 始终与 oldVal 相同
  • @brettvd 所以在我的自定义指令中,我将“divs”传递给数据。此数据绑定到自定义指令控制器到 ctrl.data...
  • 每次数据更改时都会触发吗? newVal/oldVal 是否匹配之前或之后的数据状态?您是否尝试过在 Plunkr 或 JSFidde 等更简单的代码中复制它?

标签: javascript angularjs carousel angular-ui-bootstrap


【解决方案1】:

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

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

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

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

【讨论】:

  • 我想在 oldValue 中看到 undefined 的原因是因为在我的手表里面我有一个 if (newVal !== oldVal)。由于在初始化期间 oldVal 与 newVal 相同,因此我从来没有将我的数据放入我的指令中......但我明白你的意思,我意识到我不应该首先依赖 watch 来初始化我的数据。
  • 确实如此。要么删除该条件,因为它唯一为假是在初始化期间,或者通常执行if (newVal !== oldVal) { scope.init(); } 之类的操作,然后在观察程序之外调用scope.init(),并使用该函数中的所有逻辑。
  • 删除 newVal !== oldVal 肯定会起作用 :) 我意识到在初始化期间我的数据尚未初始化,就像这篇文章所说的 *.com/questions/20465840/… 但也许是其他原因导致它。总之非常感谢! :)
【解决方案2】:

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

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

在观察者注册到作用域后,监听器 fn 是 异步调用(通过 $evalAsync)来初始化观察者。在 在极少数情况下,这是不可取的,因为在调用监听器时 watchExpression 的结果没有改变。检测这种情况 在侦听器 fn 中,您可以比较 newVal 和 oldVal。如果 这两个值是相同的(===)然后监听器被称为到期 初始化

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

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

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

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

编辑:

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

  1. &lt;a1 prop="data[0]"&gt;&lt;/a1&gt;
    • HTML 看起来与编写时一样。 data=[]。指令元素存在,用data[0]=undefined 调用link$watchprop=undefined 调用
  2. &lt;!-- ngRepeat: element in data track by $index --&gt;
    • HTML 只是一个注释。等待data 被填充。不存在指令元素,这意味着 link 未被调用

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

  1. &lt;a1 prop="data[0]"&gt;&lt;/a1&gt;
    • 同上。 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 函数现在已填充。 $watch 调用时使用 prop 链接的值

【讨论】:

  • 但不应该以newVal = undefinedoldVal = undefined作为初始化第一次调用手表吗?
  • 并非总是如此。就像它在文档中所说的那样:“在观察者注册到范围后,监听器 fn 被异步调用(通过 $evalAsync)来初始化观察者”。当您在指令中注册 $watch 函数时,这些值已被链接。因此,如前所述,初始值是相等的,但碰巧有一个值。如果它总是通过undefined,我认为文档会建议检查它而不是if ( newValue !== oldValue )
  • 已编辑以解释您所看到的 ng-repeat 差异
最近更新 更多