在ng-repeat中使用指令,以及作用域“@”的神秘力量
如果你喜欢在工作代码中看到问题,请从这里开始: 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-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>
我的问题是:
- 为什么long-version不能在ng-repeat中工作?
- 你怎么能在ng-repeat里面做这个longhand版本?
在drinkLonghand
,你使用的代码
scope.flavor = attrs.flavor;
在链接阶段,内插属性还没有被评估,所以它们的值是undefined
。 (他们在ng-repeat
之外工作,因为在这些情况下,你没有使用string插值;你只是传递一个普通的普通string,例如“草莓”。)这是指令开发人员指南中提到的,方法在API文档中不存在的名为$observe
Attributes
:
使用
$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
的范围上的预先存在的variables。 添加空白隔离范围也不起作用; 这是因为Angular试图根据指令的作用域插入string,在这个作用域上没有叫做flav
属性。 (您可以通过添加scope.flav = 'test';
在对attrs.$observe
的调用之scope.flav = 'test';
。
当然,你可以用一个隔离作用域定义来解决这个问题
scope: { flav: '@flavor' }
或者通过创build一个非孤立的子范围
scope: true
或者不依赖于具有{{flavor}}
的template
,而是像直接的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
的初始值scope.$parent.flav
; 如果这个值能够改变的话,你必须使用$watch
并重新评估插值函数fn
的结果(我没有积极的离开我的头顶,你怎么知道要看什么;你可能只是必须通过一个函数)。 scope: { flavor: '@' }
是避免pipe理所有这些复杂性的一个很好的捷径。
[更新]
从评论中回答这个问题:
快捷方式如何在幕后解决这个问题? 它是像你一样使用$ 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
可以在内部被告知使用与当前不同的范围来将属性观察build立在(下一个最后一行,在break
之上)的基础上。 虽然自己可能很想使用它,但请记住,任何带有双美元的$$
前缀的东西都应该被认为是Angular的私有API的私有API,并且可以随时更改而不会发出警告(更不用说,使用@
模式)。