angularjs指令中的棘手范围绑定

时间:2013-01-05 13:04:33

标签: angularjs

我想在angularjs中编写'edit in place'指令。 我希望该指令是可重用的,因此我对该指令有以下要求:

  1. 它必须是一个可以解散任何元素的地方,这是有道理的(div,span,li)
  2. 它必须支持编辑按钮,点击它会将显示元素设置为输入文件。通常是一个对象的属性,例如联系人(号码,姓名)
  3. 我在指令中揭示了范围可见性的欺骗行为,可以在这个小提琴http://jsfiddle.net/honzajde/ZgNbU/1/中看到。

    1. 在指令中填写:模板和范围 - >显示contact.number和contact.name
    2. 在指令中填写:范围 - >仅显示contact.number
    3. 不评论任何内容 - >什么都没有显示
    4. =>当两者都被注释掉时,只需在指令中添加模板就可以渲染contact.number,即使没有使用模板。

      我在问游戏的规则是什么?

      <div>
        <div ng-controller="ContactsCtrl">
          <h2>Contacts</h2>
          <br />
          <ul>
              <li ng-repeat="contact in contacts">
                  <span edit-in-place="" ng-bind="contact.number"></span> | 
                  <span edit-in-place="" >{{contact.name}}</span>
              </li>
          </ul>
          <br />
          <p>Here we repeat the contacts to ensure bindings work:</p>
          <br />
          <ul>
              <li ng-repeat="contact in contacts">
                  {{contact.number}} | {{contact.name}}
              </li>
          </ul>
      
        </div>
      </div>
      
      
      var app = angular.module( 'myApp', [] );
      
      app.directive( 'editInPlace', function() {
        return {
          restrict: 'A',
          //scope: { contact:"=" },    
          template: '<span ng-click="edit()" ng-bind="value"></span><input ng-model="value"></input>',
          link: function ( $scope, element, attrs ) {
            // Let's get a reference to the input element, as we'll want to reference it.
            var inputElement = angular.element( element.children()[1] );
      
            // This directive should have a set class so we can style it.
            element.addClass( 'edit-in-place' );
      
            // Initially, we're not editing.
            $scope.editing = false;
      
            // ng-click handler to activate edit-in-place
            $scope.edit = function () {
              $scope.editing = true;
      
              // We control display through a class on the directive itself. See the CSS.
              element.addClass( 'active' );
      
              // And we must focus the element. 
              // `angular.element()` provides a chainable array, like jQuery so to access a native DOM function, 
              // we have to reference the first element in the array.
              inputElement[0].focus();
            };
      
            // When we leave the input, we're done editing.
            inputElement.prop( 'onblur', function() {
              $scope.editing = false;
              element.removeClass( 'active' );
            });
          }
        };
      });
      
      app.controller('ContactsCtrl', function ( $scope ) {
        $scope.contacts = [
          { number: '+25480989333', name: 'sharon'},
          { number: '+42079872232', name: 'steve'}
        ];
      });
      

2 个答案:

答案 0 :(得分:8)

你遇到了问题,因为你误用角度。

首先,指令应该是自包含的,但是你要从中提取功能,这使得更少通用,更少可重用。在您的代码中,您在DOM和控制器中具有属于指令的功能。为什么?

其次,你的标记也不清楚,javascript特别希望你想要在所有这些片段串在一起时完成。

第三,在大多数情况下,指令应该有自己的独立范围,这是通过声明一个带有应该绑定的属性的范围对象来完成的。您不应该在指令中传递表达式(即{{contact.name}}),因为它将打破绑定,并且当编辑就地结束时您的联系人不会更新。正确的方法是通过范围上的=属性建立双向绑定。 ng-bind不是您想要的:特定范围,因此我们在指令的范围内使用 。正如Valentyn建议的那样,你可以做一些魔术来解决这个问题,但这并不是一个好主意,而且设置正确的方式非常简单。通过属性执行此操作的问题是什么?

这都是糟糕的Ju-ju。

正如我在other question on this same topic中指出的那样,你必须使你的指令自成一体,用角度而不是反对它。这是我之前给你的attribute-based version of the fiddle,符合你的第一个要求。请让我知道这个实现有什么问题,我们可以谈谈修复它的角度方法。

最后,如果您根据&#34;按钮&#34;进一步提供您需要的内容,我也会将其纳入小提琴中。


[更新]

有可能使指令按照您的方式运行,但最终会遇到问题(或者现在看来会出现问题)。角度应用程序(或任何应用程序)中的所有组件应尽可能自包含。它不是&#34;规则&#34;或限制;它是一个&#34;最佳实践&#34;。类似地,指令组件之间的通信可以通过控制器发生,但它不应该。理想情况下,您根本不应该在控制器中引用DOM - 这是指令的用途。

如果您的特定目的是可编辑的,则 是您的指令。可以使用较大指令使用的较低级别通用编辑就地指令,但仍然存在更高级别的指令。更高级别的指令封装了它们之间的逻辑。然后,这个更高级别的组件需要一个联系对象。

最后,不,ng-bind="var"{{var}}之间不一定存在很大差异。但那不是问题;问题是 那个绑定发生了。在您的示例中,传递给指令而不是双向绑定的变量。我的观点是指令需要访问变量,以便它可以改变它。

摘要:您正在以非常jQuery风格的方式进行编码。这对于jQuery中的编码非常有用,但是在Angular编码时它并没有那么好用。事实上,它会导致许多问题,例如您正在经历的问题。例如,在jQuery中,您可以手动插入DOM元素,声明和处理事件,并在一个代码块中手动绑定变量。在Angular中,关注点清晰分离,大多数绑定都是自动的。在大多数情况下,它导致javascript代码至少比jQuery替代方案小三分之二。这是其中一种情况。

也就是说,我创建了一个Plunker,它包含了一个更复杂的编辑版本以及一个新的更高级别指令,以包含其他功能:http://plnkr.co/edit/LVUIQD?p=preview

我希望这会有所帮助。

[更新2]

这些是您新一轮问题的答案。它们可能对你的启发有好处,但我已经给你了#34;棱角分明的方式&#34;解决你的问题。您还会发现我在原始答案和更新中已经解决了这些问题(更广泛的描述)。希望这更加明显。

问题:&#34;在指令中注释:模板和范围 - &gt; contact.number和contact.name显示&#34;

我的回复:当您未指定范围时,该指令会继承其父范围。您在父的上下文中绑定并插入了名称和数字,因此它&#34;工作&#34;。但是,因为该指令将改变该值,所以 不是解决它的好方法。它确实应该有自己的范围。

问题:&#34;在指令中填写:范围 - &gt;仅显示contact.number&#34;

我的回复:您将父级的范围属性绑定到&#34; contact.number&#34;指令,因此它将在$ digest循环中放入 - 在指令处理完毕后。在&#34; contact.name&#34;上,将放在指令中,只有在指令代码进行转换时才能正常工作。

问题:&#34;没有评论任何内容 - &gt;没有显示任何内容&#34;

我的回复:是的。如果指令有自己的范围(并且肯定应该这样),那么必须使用已定义的指令范围属性来传递值,正如我的几个代码示例所示。但是,当我们通过在其定义中使用scope属性明确禁止时,您的代码会尝试在指令中使用父作用域。

摘要:虽然第二次更新可能会提供信息(我希望它是这样),但它并没有回答 下面的问题:如何做我正确使用角度组件 ,以便我使用的范围始终是我认为的范围?我的第一篇文章和随后的更新,回答了这个问题。

答案 1 :(得分:2)

以下是您的小提琴,但需要进一步改进以满足您的要求的完整列表:http://jsfiddle.net/5VRFE/

关键点是:

scope: { value:"=editInPlace" },

一些注意事项:使用ng-show ng-hide directivies进行视觉显示隐藏而不是更改css类更好。此外,最好将功能扩展到不同的指令中以更好地分离关注点(检查ngBlur指令)

关于您对范围检查的混淆guide about scopes段“了解移植和范围”:如果您想要访问,则每个指令都有单独的隔离范围指令的模板到控制器的范围使用指令范围binging(指令定义对象的“范围”字段)。并且transcluded元素的范围也包括您定义的transcluding模板。

从第一个视图来看,这些孤立的范围听起来有点奇怪,但是当你有良好的结构化指令时(注意一个指令可以要求另一个指令并共享绑定),你会发现它非常有用。