采用NG-重复内一个指令,并且范围一种神秘的力量“@”(Using a directive insi

2019-09-02 16:28发布

如果你喜欢看问题的工作代码,从这里开始: 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-重复中使用时,只有快捷版本的作品:

  <!-- 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重复里面工作?
  2. 你如何才能让NG重复里面的手写版的工作?

Answer 1:

drinkLonghand ,您使用的代码

scope.flavor = attrs.flavor;

在链接阶段,插值属性还没有得到评估,因此它们的值是undefined 。 (他们在外面工作的ng-repeat ,因为在你没有使用字符串插值的情况下,你只是传递一个普通平凡的字符串,如“草莓”。)这是中提到的指令开发人员指南用,沿在方法Attributes是不存在的API文档中称为$observe

使用$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 。 添加空白分离范围也不起作用; 这是因为角试图插值基于指令的范围,在其中有没有所谓的属性中的字符串flav 。 (您可以通过添加测试这个scope.flav = 'test';调用上述attrs.$observe

当然,你可以用一个隔离的范围定义解决这个问题像

scope: { flav: '@flavor' }

或者通过创建一个非孤立子范围

scope: true

或者通过不依赖于template{{flavor}}而是做一些直接的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 ; 如果该值是能够改变,你必须使用$watch并重新评估插值功能的结果fn (我还不能肯定把我的头顶部你怎么知道什么$watch ,你可能只是具有在函数来传递)。 scope: { flavor: '@' }是一个很好的快捷方式,以避免管理所有这些复杂性。

[更新]

要回答的意见的问题:

快捷方法解决如何在幕后这个问题? 难道使用$插值服务为你做呢,还是做别的什么东西?

我不知道这一点,所以我看着源。 我发现在以下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 )。 虽然它可能是诱人这个自己使用,请记住,与双元什么$$前缀应被视为私有角的私人API,并随时更改,恕不警告(别说你得到这个免费反正当使用@模式)。



文章来源: Using a directive inside an ng-repeat, and a mysterious power of scope '@'