在 ng-repeat 中使用指令,以及神秘的作用域“@" [英] Using a directive inside an ng-repeat, and a mysterious power of scope '@'

查看:28
本文介绍了在 ng-repeat 中使用指令,以及神秘的作用域“@"的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

如果您希望在工作代码中看到问题,请从这里开始:http://jsbin.com/ayigub/2/edit

考虑用这种几乎等效的方式来编写一个简单的指令:

app.directive("drinkShortcut", function() {返回 {范围:{味道:'@'},模板:'<div>{{flavor}}</div>'};});app.directive("drinkLonghand", function() {返回 {范围: {},模板:'<div>{{flavor}}</div>',链接:函数(范围,元素,属性){scope.flavor = attrs.flavor;}};});

当单独使用时,这两个指令的工作和行为完全相同:

 <!-- 这有效--><div Drink-shortcut flavor="blueberry"></div><小时/><!-- 这有效--><div Drink-longhand flavor="strawberry"></div><小时/>

但是,当在 ng-repeat 中使用时,只有快捷方式有效:

 <!-- 在重复中使用快捷方式也有效--><div ng-repeat="flav in ['cherry', 'grape']"><div Drink-shortcut flavor="{{flav}}"></div>

<小时/><!-- 但是:在重复中使用普通方法是行不通的--><div ng-repeat="flav in ['cherry', 'grape']"><div Drink-longhand flavor="{{flav}}"></div>

我的问题是:

  1. 为什么手写版本在 ng-repeat 中不起作用?
  2. 你如何让手写版本在 ng-repeat 中工作?

解决方案

drinkLonghand中,你使用代码

scope.flavor = attrs.flavor;

在链接阶段,内插属性还没有被评估,所以它们的值是undefined.(它们在 ng-repeat 之外工作,因为在这些情况下你没有使用字符串插值;你只是传入一个普通的普通字符串,例如草莓".)这在Directives 开发者指南,以及不存在的 Attributes 方法在 API 文档中 称为 $observe:

<块引用>

使用 $observe 观察包含插值的属性的值变化(例如 src="{{bar}}").这不仅非常有效,而且还是轻松获取实际值的唯一方法,因为在链接阶段尚未评估插值,因此此时将值设置为 undefined.

因此,要解决这个问题,您的 drinkLonghand 指令应如下所示:

app.directive("drinkLonghand", function() {返回 {模板:'<div>{{flavor}}</div>',链接:函数(范围,元素,属性){attrs.$observe('flavor', function(flavor) {scope.flavor = 风味;});}};});

然而,问题在于它没有使用隔离作用域;因此,该行

scope.flavor = 风味;

有可能覆盖名为 flavor 的作用域上预先存在的变量.添加一个空白的隔离范围也不起作用;这是因为 Angular 尝试根据指令的范围插入字符串,在该范围上没有名为 flav 的属性.(您可以通过在对 attrs.$observe 的调用上方添加 scope.flav = 'test'; 来测试这一点.)

当然,您可以使用隔离范围定义来解决此问题

scope: { flav: '@flavor' }

或者通过创建一个非隔离的子作用域

范围:真

或者不依赖带有 {{flavor}}template 而是做一些直接的 DOM 操作,比如

attrs.$observe('flavor', function(flavor) {element.text(风味);});

但这违背了练习的目的(例如,只使用 drinkShortcut 方法会更容易).因此,为了使该指令起作用,我们将打破 $interpolate service 自己在指令的 $parent 范围内进行插值:

app.directive("drinkLonghand", function($interpolate) {返回 {范围: {},模板:'<div>{{flavor}}</div>',链接:函数(范围,元素,属性){//element.attr('flavor') == '{{flav}}'//`flav` 是在 ng-repeat 的 `scope.$parent` 上定义的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 = 匹配[2]||范围名称,mode = match[1],//@、= 或 &最后一个值,父获取,父集;开关(模式){案件 '@': {attrs.$observe(attrName, function(value) {范围[范围名称] = 值;});attrs.$$observers[attrName].$$scope = parentScope;休息;}

因此似乎可以在内部告诉 attrs.$observe 使用与当前范围不同的范围来基于属性观察(最后一行的下一行,在 break 上方)).虽然自己使用它可能很诱人,但请记住,任何带有双美元 $$ 前缀的东西都应该被认为是 Angular 私有 API 的私有,并且可能会在没有警告的情况下更改(更不用说在使用 @ 模式时,无论如何你都可以免费获得它).

If you prefer to see the question in working code, start here: http://jsbin.com/ayigub/2/edit

Consider this almost equivalent ways to write a simple 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;
    }
  };
});

When used by themselves, the two directives work and behave identically:

  <!-- This works -->
  <div drink-shortcut flavor="blueberry"></div>
  <hr/>

  <!-- This works -->
  <div drink-longhand flavor="strawberry"></div>
  <hr/>

However, when used within an ng-repeat, only the shortcut version works:

  <!-- 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>

My questions are:

  1. Why does the longhand version not work inside an ng-repeat?
  2. How could you make the longhand version work inside an ng-repeat?

解决方案

In drinkLonghand, you use the code

scope.flavor = attrs.flavor;

During the linking phase, interpolated attributes haven't yet been evaluated, so their values are undefined. (They work outside of the ng-repeat because in those instances you aren't using string interpolation; you're just passing in a regular ordinary string, e.g. "strawberry".) This is mentioned in the Directives developer guide, along with a method on Attributes that is not present in the API documentation called $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 to undefined.

So, to fix this problem, your drinkLonghand directive should look like this:

app.directive("drinkLonghand", function() {
  return {
    template: '<div>{{flavor}}</div>',
    link: function(scope, element, attrs) {
      attrs.$observe('flavor', function(flavor) {
        scope.flavor = flavor;
      });
    }
  };
});

However, the problem with this is that it doesn't use an isolate scope; thus, the line

scope.flavor = flavor;

has the potential to overwrite a pre-existing variable on the scope named flavor. Adding a blank isolate scope also doesn't work; this is because Angular attempts to interpolate the string on based on the directive's scope, upon which there is no attribute called flav. (You can test this by adding scope.flav = 'test'; above the call to attrs.$observe.)

Of course, you could fix this with an isolate scope definition like

scope: { flav: '@flavor' }

or by creating a non-isolate child scope

scope: true

or by not relying on a template with {{flavor}} and instead do some direct DOM manipulation like

attrs.$observe('flavor', function(flavor) {
  element.text(flavor);
});

but that defeats the purpose of the exercise (e.g. it'd be easier to just use the drinkShortcut method). So, to make this directive work, we'll break out the $interpolate service to do the interpolation ourself on the directive's $parent scope:

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);
    }
  };
});

Of course, this only works for the initial value of scope.$parent.flav; if the value is able to change, you'd have to use $watch and reevaluate the result of the interpolate function fn (I'm not positive off the top of my head how you'd know what to $watch; you might just have to pass in a function). scope: { flavor: '@' } is a nice shortcut to avoid having to manage all this complexity.

[Update]

To answer the question from the comments:

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?

I wasn't sure about this, so I looked in the source. I found the following in 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;
     }

So it seems that attrs.$observe can be told internally to use a different scope than the current one to base the attribute observation on (the next to last line, above the break). While it may be tempting to use this yourself, keep in mind that anything with the double-dollar $$ prefix should be considered private to Angular's private API, and is subject to change without warning (not to mention you get this for free anyway when using the @ mode).

这篇关于在 ng-repeat 中使用指令,以及神秘的作用域“@"的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
前端开发最新文章
热门教程
热门工具
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆