如果您希望在工作代码中看到问题,请从此处开始:http : //jsbin.com/ayigub/2/edit
考虑以下几乎等效的方式来编写简单的指令:
app.directive("drinkShortcut", function() {
return {
scope: { flavor: '@'},
template: '<div>{{flavor}}</div>'
};
});
app.directive("drinkLonghand", function() {
return {
scope: {},
template: '<div>{{flavor}}</div>',
link: function(scope, element, attrs) {
scope.flavor = attrs.flavor;
}
};
});
单独使用时,这两个指令的工作方式和行为相同:
<!-- This works -->
<div drink-shortcut flavor="blueberry"></div>
<hr/>
<!-- This works -->
<div drink-longhand flavor="strawberry"></div>
<hr/>
但是,在ng-repeat中使用时,仅快捷方式版本有效:
<!-- Using the shortcut inside a repeat also works -->
<div ng-repeat="flav in ['cherry', 'grape']">
<div drink-shortcut flavor="{{flav}}"></div>
</div>
<hr/>
<!-- HOWEVER: using the longhand inside a repeat DOESN'T WORK -->
<div ng-repeat="flav in ['cherry', 'grape']">
<div drink-longhand flavor="{{flav}}"></div>
</div>
我的问题是:
在中drinkLonghand
,您使用代码
scope.flavor = attrs.flavor;
在链接阶段,尚未评估插值属性,因此其值为undefined
。(他们的工作之外的ng-repeat
,因为在这些情况下,您不使用字符串插值;你只是传递一个普通平凡的字符串,如“草莓”。)这是中提到的指令开发人员指南,有一个方法沿着Attributes
该名为的API文档中不存在$observe
:
使用
$observe
观察到,含有插(如属性值的变化src="{{bar}}"
)。这不仅非常有效,而且还是轻松获取实际值的唯一方法,因为在链接阶段尚未评估插值,因此此时将值设置为undefined
。
因此,要解决此问题,您的drinkLonghand
指令应如下所示:
app.directive("drinkLonghand", function() {
return {
template: '<div>{{flavor}}</div>',
link: function(scope, element, attrs) {
attrs.$observe('flavor', function(flavor) {
scope.flavor = flavor;
});
}
};
});
但是,这样做的问题是它没有使用隔离范围。因此,线
scope.flavor = flavor;
有可能覆盖名为的作用域上的预先存在的变量flavor
。添加空白隔离范围也不起作用;这是因为Angular尝试根据指令的作用域对字符串进行插值,在该作用域上没有调用属性flav
。(您可以通过scope.flav = 'test';
在上方添加来测试attrs.$observe
。)
当然,您可以使用隔离范围定义来解决此问题,例如
scope: { flav: '@flavor' }
或通过创建非隔离子范围
scope: true
或者不依赖于template
with {{flavor}}
而是直接进行一些DOM操作,例如
attrs.$observe('flavor', function(flavor) {
element.text(flavor);
});
但这违反了练习的目的(例如,使用该drinkShortcut
方法会更容易)。因此,为了使该指令起作用,我们将扩展$interpolate
服务以在指令$parent
范围内自行进行插值:
app.directive("drinkLonghand", function($interpolate) {
return {
scope: {},
template: '<div>{{flavor}}</div>',
link: function(scope, element, attrs) {
// element.attr('flavor') == '{{flav}}'
// `flav` is defined on `scope.$parent` from the ng-repeat
var fn = $interpolate(element.attr('flavor'));
scope.flavor = fn(scope.$parent);
}
};
});
当然,这仅适用于的初始值scope.$parent.flav
;如果该值能够更改,则您必须使用$watch
并重新评估插值函数的结果fn
(我不是很想知道如何知道$watch
;您可能只需要传递一个功能)。scope: { flavor: '@' }
是避免必须管理所有这些复杂性的好捷径。
[更新]
要从评论中回答问题:
快捷方式如何在后台解决此问题?是像您一样使用$ interpolate服务,还是在做其他事情?
我对此不太确定,因此我查看了源代码。我在中找到以下内容compile.js
:
forEach(newIsolateScopeDirective.scope, function(definiton, scopeName) {
var match = definiton.match(LOCAL_REGEXP) || [],
attrName = match[2]|| scopeName,
mode = match[1], // @, =, or &
lastValue,
parentGet, parentSet;
switch (mode) {
case '@': {
attrs.$observe(attrName, function(value) {
scope[scopeName] = value;
});
attrs.$$observers[attrName].$$scope = parentScope;
break;
}
因此,似乎attrs.$observe
可以在内部告诉我们,使用与当前范围不同的范围作为属性观察的基础(最后一行的下一行,在上方break
)。尽管可能自己想使用它,但请记住,任何带有双美元$$
前缀的东西都应视为Angular私有API的私有内容,并且如有更改,恕不另行通知(更不用说您在使用@
模式)。
本文收集自互联网,转载请注明来源。
如有侵权,请联系 [email protected] 删除。
我来说两句