如果你喜欢看问题的工作代码,从这里开始: http://jsbin.com/ayigub/2/edit
考虑这个几乎等同的方式来写一个简单的direcive:
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-重复中使用时,只有快捷版本的作品:
<!-- 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>
我的问题是:
- 为什么手写版本不是NG重复里面工作?
- 你如何才能让NG重复里面的手写版的工作?
在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
。 添加空白分离范围也不起作用; 这是因为角试图插值基于指令的范围,在其中有没有所谓的属性中的字符串flav
。 (您可以通过添加测试这个scope.flav = 'test';
调用上述attrs.$observe
)
当然,你可以用一个隔离的范围定义解决这个问题像
scope: { flav: '@flavor' }
或者通过创建一个非孤立子范围
scope: true
或者通过不依赖于template
与{{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: '@' }
是一个很好的快捷方式,以避免管理所有这些复杂性。
[更新]
要回答的意见的问题:
快捷方法解决如何在幕后这个问题? 难道使用$插值服务为你做呢,还是做别的什么东西?
我不知道这一点,所以我看着源。 我发现在以下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
)。 虽然它可能是诱人这个自己使用,请记住,与双元什么$$
前缀应被视为私有角的私人API,并随时更改,恕不警告(别说你得到这个免费反正当使用@
模式)。