javascript - 在 ng-repeat 中使用指令,以及范围 '@' 的神秘力量

标签 javascript angularjs angularjs-directive

如果您希望在工作代码中查看问题,请从这里开始: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>

我的问题是:

  1. 为什么普通版本在 ng-repeat 中不起作用?
  2. 如何让手写版本在 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 to 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 的属性。 (您可以通过在 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/

相关文章:

javascript - 在javascript中迭代数组和concat

javascript - Tampermonkey 脚本无法修改表单控件

javascript - 如何处理我类的点击事件

javascript - 在 AngularJS 中,如果对远程资源的响应状态为 403(禁止),如何隐藏 DIV

javascript - 动态ng-click不调用函数

javascript - 无法关闭 JS 弹出窗口

javascript - 始终显示 3 位数字和描述符

angularjs - 使用OAuth2提供程序的独立域上的API和Auth服务器进行SPA( Angular )身份验证; Google,Facebook

javascript - 在 AngularJS 中更新模块值或重新注册过滤器

javascript - 计算 Angular js中表单中必填字段的数量?