如果您希望在工作代码中查看问题,请从这里开始: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>
我的问题是:
- 为什么普通版本在 ng-repeat 中不起作用?
- 如何让手写版本在 ng-repeat 中工作?
最佳答案
在drinkLonghand
中,您使用代码
scope.flavor = attrs.flavor;
在链接阶段,插值属性尚未被评估,因此它们的值是未定义
。 (它们在 ng-repeat
之外工作,因为在那些情况下你没有使用字符串插值;你只是传递一个普通的普通字符串,例如“strawberry”。)这在Directives developer guide ,以及不存在的 Attributes
方法 in the API documentation称为$observe
:
Use
$observe
to observe the value changes of attributes that contain interpolation (e.g.src="{{bar}}"
). Not only is this very efficient but it's also the only way to easily get the actual value because during the linking phase the interpolation hasn't been evaluated yet and so the value is at this time set toundefined
.
因此,要解决这个问题,您的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
的属性。 (您可以通过在 attrs.$observe
调用上方添加 scope.flav = 'test';
来测试它。)
当然,您可以使用像这样的隔离范围定义来解决这个问题
scope: { flav: '@flavor' }
或者通过创建一个非隔离的子作用域
scope: true
或者不依赖于 {{flavor}}
的模板
,而是直接进行一些 DOM 操作,例如
attrs.$observe('flavor', function(flavor) {
element.text(flavor);
});
但这违背了练习的目的(例如,只使用 drinkShortcut
方法会更容易)。因此,为了使该指令起作用,我们将分解 $interpolate
service在指令的 $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
的初始值有效;如果该值能够更改,则必须 use $watch
并重新评估插值函数 fn
的结果(我不确定你怎么知道 $watch
的内容;你可能只需要传入一个函数)。 scope: { flavor: '@' }
是一个很好的捷径,可以避免管理所有这些复杂性。
[更新]
回答评论中的问题:
How is the shortcut method solving this problem behind the scenes? Is it using the $interpolate service as you did, or is it doing something else?
我不确定这一点,所以我查看了源代码。我在 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 私有(private) API 私有(private)的,并且可能会在没有警告的情况下发生变化(更不用说在使用 @
模式时,无论如何你都可以免费获得它)。
关于javascript - 在 ng-repeat 中使用指令,以及范围 '@' 的神秘力量,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/16502559/