1

我想在 angularjs 中编写“就地编辑”指令。我希望该指令是可重用的,因此我对该指令有以下要求:

  1. 它必须是可以装饰任何元素的属性,这是有意义的(div,span,li)
  2. 它必须支持编辑按钮,单击该按钮会将设置的显示元素更改为输入文件。一个对象的典型属性,例如联系人(号码、姓名)

我发现指令中范围可见性的欺骗行为可以在这个小提琴http://jsfiddle.net/honzajde/ZgNbU/1/中看到。

  1. 在指令中注释掉:显示模板和范围 -> 联系人号码和联系人姓名
  2. 在指令中注释掉:scope -> contact.number only 被显示
  3. 不注释掉任何东西->什么都不显示

=> 当两者都被注释掉时,只需将模板添加到指令中,即使未使用模板,它也会呈现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'}
  ];
});
4

2 回答 2

8

您遇到问题是因为您滥用了角度。

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

其次,当所有这些部分串在一起时,您的标记和javascript也不清楚您想要完成的具体操作。

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

这都是坏的朱珠。

正如我在您关于同一主题的其他问题中指出的那样,您必须使您的指令自包含并使用角度,而不是反对它。这是我之前给你的小提琴的基于属性的版本,满足你的第一个要求。请让我知道这个实现具体有什么问题,我们可以讨论修复它的角度方式。

最后,如果您提供有关“按钮”方面所需内容的更多上下文,我也会将其合并到小提琴中。


[更新]

可以让指令按照你的方式工作,但你最终会遇到问题(或者现在看起来)。Angular 应用程序(或与此相关的任何应用程序)中的所有组件都应尽可能独立。这不是“规则”或限制;这是一个“最佳实践”。类似地,指令组件之间的通信可以通过控制器进行,但不应该这样。理想情况下,您根本不应该在控制器中引用 DOM - 这就是指令的用途。

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

ng-bind="var"最后,不,和之间不一定有很大区别{{var}}。但这不是问题;问题是绑定发生在哪里。在您的示例中,将而不是双向绑定变量传递给指令。我的观点是该指令需要访问该变量,以便它可以更改它。

摘要:您正在以一种非常 jQuery 风格的方式进行编码。这对于在 jQuery 中编码非常有用,但在 Angular 中编码时效果不佳。实际上,它会导致很多问题,例如您遇到的问题。例如,在 jQuery 中,您可以在单个代码块中动态插入 DOM 元素、声明和处理事件以及手动绑定变量,所有这些都是手动操作。在 Angular 中,关注点完全分离,并且大部分绑定是自动的。在大多数情况下,它导致 javascript 代码比 jQuery 替代方案至少小三分之二。这是其中一种情况。

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

我希望这有帮助。

[更新 2]

这些是您新一轮问题的答案。它们可能对你的启迪有好处,但我已经给了你“角度方式”来解决你的问题。您还会发现,我在最初的答案和更新中已经解决了这些问题(以更广泛的方式)。希望这使它更加明显。

问题: “在指令中注释掉:模板和范围 -> 显示contact.number 和contact.name”

我的回复:当您不指定范围时,该指令会继承其父范围。您在 parent 的上下文中绑定并插入了名称和编号,因此它“有效”。但是,因为指令会改变值,所以这不是解决问题的好方法。它确实应该有自己的范围。

问题: “在指令中注释掉:范围 -> 仅显示 contact.number”

我的回复:您将父级的范围属性绑定到“contact.number”指令,因此它将在 $digest 循环期间放置在内部 - 在指令被处理之后。在“contact.name”上,将其放在指令中,该指令只有在指令编码为嵌入时才有效。

问题: “没有注释掉任何东西 -> 什么都没有显示”

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

Summary: While this second update may be informative (and I hope that it is), it doesn't answer the question beneath your questions: how do I use angular components correctly so that the scope I'm using is always what I think it is? My first post and the subsequent update, answer that question.

于 2013-01-05T17:52:38.910 回答
2

这里有点更新你的小提琴,但它需要进一步改进以满足你的要求的完整列表:http: //jsfiddle.net/5VRFE/

关键点是:

scope: { value:"=editInPlace" },

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

关于范围检查指南关于范围段落“理解嵌入和范围”的混淆:如果您想从指令的模板访问控制器的范围,则每个指令都有单独的隔离范围,请使用指令范围绑定(指令定义对象的“范围”字段) . 并且被嵌入的元素也具有您定义嵌入模板的范围。

从第一个角度看,那些孤立的作用域听起来有点奇怪,但是当你有良好的结构化指令时(还要注意一个指令可能需要另一个指令并共享绑定),你会发现它非常有用。

于 2013-01-05T14:11:26.507 回答