【问题标题】:Using a directive inside an ng-repeat, and a mysterious power of scope '@'在 ng-repeat 中使用指令,以及范围“@”的神秘力量
【发布时间】:2013-05-06 08:04:19
【问题描述】:

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

【问题讨论】:

    标签: javascript angularjs angularjs-directive


    【解决方案1】:

    drinkLonghand,你使用代码

    scope.flavor = attrs.flavor;
    

    在链接阶段,尚未评估内插属性,因此它们的值为undefined。 (它们在ng-repeat 之外工作,因为在这些情况下您没有使用字符串插值;您只是传入一个普通的普通字符串,例如“strawberry”。)Directives developer guide 中提到了这一点,以及Attributes 上不存在 in the API documentation 的方法称为 $observe

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

    所以,要解决 this 问题,您的 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
    

    或者不依赖 template{{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: '@' } 是一个很好的捷径,可以避免管理所有这些复杂性。

    [更新]

    回答cmets的问题:

    捷径是如何在幕后解决这个问题的?它是像您一样使用 $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 使用与当前范围不同的范围来作为属性观察的基础(倒数第二行,在break 上方)。虽然您自己可能很想使用它,但请记住,任何带有双美元 $$ 前缀的东西都应该被视为 Angular 的私有 API 的私有内容,并且可能会在没有警告的情况下进行更改(更不用说无论如何您都可以免费获得它使用@ 模式时)。

    【讨论】:

    • 布兰登,这是一个非常棒的答案,谢谢。只是一个后续问题(随时添加为编辑):快捷方法如何在幕后解决这个问题?它是像您一样使用 $interpolate 服务,还是在做其他事情?
    • 谢谢!很高兴你这么认为。我不确定,所以我找到了解决方案并更新了我的答案。
    • 再次感谢。对于我在 SO 上收到的一个有点困难的问题,这确实是最彻底的答案之一,正是我在寻找什么。希望我能更多地支持它。但是顺便说一句,您可能对我从今天早上开始提出的这个问题感兴趣,这是 angular 文档中的一个潜在错误,并且可能比这个更容易回答:stackoverflow.com/questions/16495684/…
    • 很好的答案!太感谢了。所以 net/net 是如果你要在 ng-repeat 中使用指令,你应该使用速记方式吗?
    • 作为这个框架中的一切,它是如何工作的,这是一个该死的谜,幸运的是SO存在并且你可以找到这些答案,但我不能停止思考这个框架怎么会这么不直观?一个人花了一半的时间寻找答案/hacks/workarounds/whateveryoucallit,我只希望我不必在我的生活中再次使用它。
    猜你喜欢
    • 2015-07-01
    • 2021-09-28
    • 1970-01-01
    • 2013-03-15
    • 1970-01-01
    • 1970-01-01
    • 2015-11-06
    • 2014-10-25
    • 2014-12-26
    相关资源
    最近更新 更多