将 ng-repeat 与指令一起使用会导致手表出现缺陷
Using ng-repeat with directive causes flaw in watch
我制定了一个新指令,它基本上是一个扩展 angular UI 轮播 bootstrap 的新轮播。这个新的轮播将在一帧中显示多个 div。我的新指令接受数组中的任何数据和每个数据的自定义 html 模板。
但是,如果我将轮播与指令一起使用,我会在指令中的 watch 看到奇怪的行为。一切正常,但我的指令中的手表总是为 newVal
和 oldVal
获取相同的值。我的意思是,这是我的轮播代码:
<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
将是我的新数据。然而,事实并非如此。数据作为双向绑定传递给传送带和自定义指令(在每个指令的范围内使用“=”运算符)。
为什么会这样?我对此进行了长期调查,以下是我的发现:
- 如果我不在我的旋转木马中使用
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中,如果你需要做一些初始化代码,你可以做两件事:
- 你可以在
$watch
函数中检查它们 是否 相等,如果它们相等,那么这是初始调用
- 或者,在
link
函数中的那个函数之外,这些值是它们在那里的初始值(因为 link
函数等同于 post-link
,这意味着 scope
值已经链接)所以你可以把你的代码放在那里
分叉你的 Plunker here。请注意,我将 alert
移到了 $watch
之外,该值仍然有效
编辑:
当它不在 ng-repeat
中并且它的设置类似于您在 Plunkr 中注释掉的代码时,您看到差异的原因是您在 $timeout
中添加了数据。当页面 最初 加载时,以下是两种类型呈现的内容:
<a1 prop="data[0]"></a1>
- HTML 看起来和写的一样。
data=[]
。指令元素存在,调用 link
和 data[0]=undefined
。 $watch
调用 prop=undefined
<!-- ngRepeat: element in data track by $index -->
- HTML 只是一个评论。等待
data
被填充。不存在指令元素,这意味着 link
未被调用
当您将项目添加到 data
超时后,它们看起来像这样:
<a1 prop="data[0]"></a1>
- 同上。
data[0]
现在已定义,因此 prop
已定义
<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
个链接的值调用
当您在 link
函数中注册 $watch
时,Angular 已经在 preLink
阶段处理了绑定,因此您永远不会看到 undefined
你的观察者第一次被执行时(初始化调用是 oldVal 和 newVal 可能相同的唯一时刻。如果观察者在 绑定解析之前 注册,则 oldValue 将是 undefined
)
如果你真的想看它,你可以覆盖compile
阶段并添加自定义preLink
方法(默认link
是 postLink
).
但我真的怀疑你是否想这样做。为什么第一次没有 undefined 是个问题?您应该尝试解释您面临的真正问题。
此外,请注意,如果传递给指令的 divs
是数组,则应使用 scope.$watchColleciton
而不是 scope.$watch
来检测数组元素的变化整个数组指针的变化。
我制定了一个新指令,它基本上是一个扩展 angular UI 轮播 bootstrap 的新轮播。这个新的轮播将在一帧中显示多个 div。我的新指令接受数组中的任何数据和每个数据的自定义 html 模板。
但是,如果我将轮播与指令一起使用,我会在指令中的 watch 看到奇怪的行为。一切正常,但我的指令中的手表总是为 newVal
和 oldVal
获取相同的值。我的意思是,这是我的轮播代码:
<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
将是我的新数据。然而,事实并非如此。数据作为双向绑定传递给传送带和自定义指令(在每个指令的范围内使用“=”运算符)。
为什么会这样?我对此进行了长期调查,以下是我的发现:
- 如果我不在我的旋转木马中使用
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中,如果你需要做一些初始化代码,你可以做两件事:
- 你可以在
$watch
函数中检查它们 是否 相等,如果它们相等,那么这是初始调用 - 或者,在
link
函数中的那个函数之外,这些值是它们在那里的初始值(因为link
函数等同于post-link
,这意味着scope
值已经链接)所以你可以把你的代码放在那里
分叉你的 Plunker here。请注意,我将 alert
移到了 $watch
之外,该值仍然有效
编辑:
当它不在 ng-repeat
中并且它的设置类似于您在 Plunkr 中注释掉的代码时,您看到差异的原因是您在 $timeout
中添加了数据。当页面 最初 加载时,以下是两种类型呈现的内容:
<a1 prop="data[0]"></a1>
- HTML 看起来和写的一样。
data=[]
。指令元素存在,调用link
和data[0]=undefined
。$watch
调用prop=undefined
- HTML 看起来和写的一样。
<!-- ngRepeat: element in data track by $index -->
- HTML 只是一个评论。等待
data
被填充。不存在指令元素,这意味着link
未被调用
- HTML 只是一个评论。等待
当您将项目添加到 data
超时后,它们看起来像这样:
<a1 prop="data[0]"></a1>
- 同上。
data[0]
现在已定义,因此prop
已定义
- 同上。
<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
个链接的值调用
- 页面现在有指令元素。在每个
当您在 link
函数中注册 $watch
时,Angular 已经在 preLink
阶段处理了绑定,因此您永远不会看到 undefined
你的观察者第一次被执行时(初始化调用是 oldVal 和 newVal 可能相同的唯一时刻。如果观察者在 绑定解析之前 注册,则 oldValue 将是 undefined
)
如果你真的想看它,你可以覆盖compile
阶段并添加自定义preLink
方法(默认link
是 postLink
).
但我真的怀疑你是否想这样做。为什么第一次没有 undefined 是个问题?您应该尝试解释您面临的真正问题。
此外,请注意,如果传递给指令的 divs
是数组,则应使用 scope.$watchColleciton
而不是 scope.$watch
来检测数组元素的变化整个数组指针的变化。