AngularJS指令与隔离作用域需要澄清

3

编辑:

在这个 plunker 中有一个功能性的示例:

http://plnkr.co/edit/GFQGP0q3o9RjLAlRANPS?p=preview

外部作用域有 $scope.name = 'Donald'

所有指令都是这样声明的:

<directive-name binding="name">

这是一个多部分问题。我正在尝试更好地理解具有对外部作用域变量的观察或绑定的隔离范围。
使用非隔离范围指令时,一切都正常:
  // [WORKS]
  .directive('noScopeWithWatch', function(){
    return {
      restrict: 'E',
      link: function(scope, lElement, attrs) {
        scope.$watch(attrs.binding, function(name){
          lElement.text('Hello ' + name);
        });
      }
    };
  })
  // returns Hello Donald

混淆的部分是当我尝试隔离作用域并保持绑定时。因此,我要求对以下一些示例为什么有效而其他示例无效进行澄清。
如果我只是添加了作用域隔离和“正常”的绑定,它会失败:
  // 1. [FAILS]
  .directive('scopeWithWatch', function(){
    return {
      restrict: 'E',
      link: function(scope, lElement, attrs) {
        scope.$watch(attrs.binding, function(name){
          lElement.text('Hello ' + name);
        });
      },
      scope: {                                   // new content
        binding: '='                             // new content
      }                                          // new content
    };
  })
  // returns Hello undefined

然而,在监视器中将绑定变量作为字符串使用可以使其正常工作:
  // 2. [WORKS]
  .directive('scopeWithWatchString', function(){
    return {
      restrict: 'E',
      link: function(scope, lElement, attrs) {
        scope.$watch('binding', function(b){     // new content
          lElement.text('Hello ' + b);
        });
      },
      scope: {
        binding: '=' 
      }
    };
  })
  // returns Hello Donald

使用绑定变量作为对象时将会失败:

  // 3. [FAILS]
  .directive('scopeWithWatchObject', function(){
    return {
      restrict: 'E',
      link: function(scope, lElement, attrs) {
        scope.$watch(binding, function(b){       // new content
          lElement.text('Hello ' + b);
        });
      },
      scope: {
        binding: '='
      }
    };
  })
  // Does not work at all
  // Console output - ReferenceError: binding is not defined

尝试在隔离作用域内引用绑定变量也不起作用,但至少不会引发异常:

  // 4. [FAILS]
  .directive('scopeWithWatchScopeObject', function(){
    return {
      restrict: 'E',
      link: function(scope, lElement, attrs) {
        scope.$watch(scope.binding, function(b){  // new content
          lElement.text('Hello ' + b);
        });
      },
      scope: {
        binding: '='
      }
    };
  })
  // returns Hello undefined

使用模板中的mustaches绑定变量是有效的:
  // 5. [WORKS]
  .directive('scopeWithTemplate', function(){
    return {
      restrict: 'E',
      template: 'Hello {{binding}}',     // new content and linker removed
      scope: {
        binding: '='
      }
    };
  })
  // returns Hello Donald

但是在链接器中尝试将它们用作mustaches是不行的。

  // 6. [FAILS]
  .directive('scopeWithWatchStringUsingMustashes', function(){
    return {
      restrict: 'E',     
      link: function(scope, lElement, attrs) {        // new content
        scope.$watch('binding', function(){           // new content
          lElement.text('Hello {{binding}}');         // new content  
        });                                           // new content
      },                                              // new content
      scope: {
        binding: '='
      }
    };
  })
  // returns Hello {{binding}}

这里是Plunker:

http://plnkr.co/edit/GFQGP0q3o9RjLAlRANPS?p=preview (我目前使用的是第78个版本,请fork一下如果你想在你的答案中使用它。)

请有人解释一下为什么有些示例可以工作而其他示例不能。


你能展示一下在HTML中如何声明指令吗? - Chandermani
请查看底部的链接。这是一个具有HTML和JavaScript的完全功能的plnkr示例。 - Presidenten
2个回答

4
这个问题有一个简单的答案,适用于这里的所有示例。 在$compile的Angular文档中对此进行了解释,但容易误解。 独立作用域的整个目的是创建仅由声明它的指令使用的作用域。 为此,会创建一个新变量,将该值存储为父作用域的别名。
有3种主要定义类型:@=&

@或@attr-将局部作用域属性绑定到DOM属性的值。其结果始终是字符串,因为DOM属性是字符串。

=或=attr-在本地范围属性和通过attr属性值定义的名称的父级作用域属性之间设置双向绑定。

&或&attr-提供一种以父作用域为上下文执行表达式的方法。

@=之间唯一的区别是双向支持。 =定义仍将返回字符串结果。 因此,您依次拥有以下内容:
  1. 独立作用域不提供对attrs集合的访问权限,它只能访问自己的独立作用域项。这行不通,因为作用域中没有attrs对象。
  2. 按预期工作,binding='name'binding:'='匹配,并且'binding'可以作为独立作用域中的别名访问。
  3. 失败,因为属性始终是字符串,它们不是Javascript对象。
  4. 失败

'隔离'范围与正常范围不同,因为它不从父范围原型继承。当创建可重用组件时,这非常有用,这些组件不应意外地读取或修改父范围中的数据。

  • 像第2个一样工作。绑定已正确匹配。模板由$compile自动处理。
  • 失败,因为lElement.text只是文本。在文本赋值中使用表达式需要在将文本发送到DOM之前进行额外的手动编译步骤,否则{{}}表达式将被视为纯文本。

  • 谢谢!非常好的答案! 不过我还是有一个问题不太明白,与示例4有关。我知道在指令中无法访问scope.name,但为什么我也无法访问scope.binding呢?按理说,scope: { binding: '=' }应该会在指令的作用域中添加一个别名为binding的变量吧? - Presidenten
    不,scope是一个“特殊”的对象,他们只是将其命名为scope以避免(或创建?)混淆。在普通控制器中,$scope对象被创建来保存所有子属性。在指令的情况下,指令本身就是它自己的作用域,因此当您引用别名属性时,实际上是self.attr,而不是scope,scope仅用作关键字告诉Angular将attrs别名化。 - Claies
    啊啊啊啊啊啊啊.....好的!它存储在其他对象中吗?还是只能通过将别名作为字符串引用来访问? - Presidenten
    嗯,让我重新表达我的最后一个问题。如果我声明scope:{binding:'='}。是否有一些对象可以让我输入$watch(xxx.binding, ...)而不是$watch('binding', ...)? - Presidenten
    1
    不,隔离作用域不是普通对象,它是一个键/值对的字典(哈希对象),因此当您提供“'binding'”键时,您将从字典中返回该值。 - Claies
    显示剩余2条评论

    2
    让我来试着解决这些问题。
    1. 这不起作用是因为$watch方法使用的是在作用域上定义的表达式。attrs.binding的值为"name",而在隔离作用域中没有name属性。
    2. 工作的原因是$watch在作用域的binding属性上。
    3. 这是不允许的。这应该是字符串。
    4. 这应该可以工作。不确定为什么这不能工作。
    5. binding在隔离作用域中被定义并且它起作用了。
    6. 当你设置lElement.text('Hello {{binding}}');时,它需要编译表达式以评估表达式,因此这种表达式将不起作用。
    当你说: <scope-with-watch binding="name"></scope-with-watch>和做隔离作用域时,绑定是在父作用域上定义的name属性和在隔离作用域上的binding属性之间进行的。

    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接