什么时候写指令?

时间:2013-04-09 10:02:47

标签: javascript angularjs angularjs-directive

Angular为我们提供了一种编写指令的机制 - 它的功能非常强大。但我一直想知道的是 - 在什么情况下你应该实际编写自己的自定义指令。

我们不断看到Stack Overflow及其周围的问题,各种人试图编写指令(我认为)不需要首先编写。在大多数情况下,它们可以通过重复,切换和显示的组合来解决。请参阅包含我认为不应该是指令的指令的问题示例!

https://stackoverflow.com/questions/16101073/angularjs-directive-is-not-working-in-ie-10

Fire button click in AngularJS

angularjs: using a directive inside the ui-bootstrap modal

一些示例场景。无论如何我都不会选择它们。因为我确信当我们应该使用/编写指令时,任何人都不清楚。

我们看到人们使用指令作为模板机制的场景。这是正确的做事方式吗?或者,还有更好的方法? (ng-include可能?)使用指令作为模板机制是否有任何好处/缺点?这个问题的原因是有时候我想知道人们是否编写指令因为来自jquery世界他们能想到的第一件事就是编写DOM操作代码,因为Angular方法是不操纵控制器中的DOM所有都倾向于写作指令中的所有代码。

编辑:

我相信这种混淆(在指令中推送内容)是因为Angular没有单独的“视图”概念 - 不像Backbone(它只有一个“视图”但没有组件!)。指令在定义组件方面是惊人的 - 但我认为如果你使用它们创建“视图”,你将失去一些“有角度”的方式。这是我的意见 - 这就是为什么我要征求其他角度社区的想法。

更简单的指令(只做一件事的指令!)的好处是它们绝对容易测试。如果你看看所有的ng指令,他们都会做一件事并做得很好。

在Angular中定义可重用“视图”(不是组件!)的最佳方法是什么?这应该写在指令中吗?或者,还有更好的方法?

如果其中一位Angular Dev在这件事上有意见,那就太棒了!

2 个答案:

答案 0 :(得分:11)

嗯......这是一个很好的问题。

我认为指令主要是“extending HTML so you can build a DSL”,提高了工作效率和代码质量。

问题是这是通过组件化来实现的。但是最重要,我们理解该指令不仅仅是关于视觉组件,不仅仅是模板化,而且关于行为

总结一下,使用指令你可以:

  1. 创建DSL以增强元素行为
  2. 创建 DSL小部件,以便您可以停止重复
  3. 包装已存在的组件,为您提供生产力。
  4. 优化
  5. 增强行为只不过是组件行为。例如,ng-click将可点击行为添加到任何元素。想象一下,你正在创建一个包含数十个可拖动元素的应用程序。比你创建一个指令来增强元素行为,使它可以拖动,甚至没有触及元素visual(<span draggable>Test</span>)。还有一个例子,想象一下你会对鼠标悬停有特别的暗示。 title属性不适用于此,您可以创建自己的my-title属性,在鼠标悬停时自动创建“特殊提示”(<span my-title="Some caption">Test</span>)。

    在开发应用程序时,您拥有大量特定于域的概念和行为。例如,Stackoverflow具有强烈的投票概念。你可以投票上/下问题,答案,评论...所以你可以创建一个可重复使用的votable指令,它会将“投票行为”和“投票控件”(向上/向下箭头)添加到praticaly任何元素。

    这最后一个给了我们另一面:模板。不仅适用于懒惰,而且可以提高DRY principle之后的代码质量和可维护性。如果您正在重复控制器代码,HTML结构或其他任何内容,为什么不进行模板化和组件化,对吧?指令是你的工作。

    当然,您也有一些通用的指令应用程序。许多应用程序(并非所有应用程序)都依赖于可点击元素,这就是为什么我们有ng-click的原因。许多应用都有上传区域。你会用jQuery的思维方式做什么?你会创建一个jQuery插件。对?在Angular中,您将创建一个Angular Widget(使用指令)。您甚至可以使用指令包装已存在的插件,再一次扩充其行为,以便它可以顺利地与您的应用程序通信。

    关于包装插件,对于每个jQuery插件(但可能是MooTools,Ext ......),你将不得不创建一个控制器,在其上调用$('element').plugin(),并关心它jQuery事件会更改并为您消化范围。这是指令的另一个完美用法。您可以创建一个指令,在您的元素上应用.plugin(),监听事件并为您更改/消化范围。这就是Angular UI Project的全部内容,请查看。

    最后一点是优化。我最近创建并创建了一个用动态列和行(网格)创建表的应用程序。问题是数据是实时更新的!并且ng-repeat内部的ng-repeat用于创建头文件,这会破坏应用程序性能(每$apply个循环中的嵌套循环,每半秒发生一次)。因此,您可以创建一个创建列模板的指令,并且仍然在其中使用ng-repeat,但只有在列版本时才会有一个循环遍历列。

    所以,总结一下,我相信指令是关于行为和形式(模板)的组件化,这会给你带来生产力和代码质量

答案 1 :(得分:2)

我亲自写了很多指令,因为它们往往使我的程序更具说明性。

JSON中的示例: - &gt;我最近制作的HTML表单解析器,我创建了一个“表单元素”指令,它解析JSON元素a创建必要的指令作为它的子节点。这样我就有了每个字段类型的指令,具有特定的行为和方法。此外,所有元素之间共享的任何常见行为都在form-element指令中。

这样,group元素是一个在其模板中带有ng-repeat的指令,title元素就像h1一样简单。但是所有都可以具有相同的条件行为(例如,只有在前一个字段具有特定值集时才能出现一个组)。而且非常干净 - 任何时候我需要添加/更改,它都保持完美,并且html非常具有声明性。

编辑 - 根据评论要求提供了一段代码。

  /**
  * Form Element
  * ============
  *
  * Handles different elements:
  *   Assigns diferent directives according to the element type
  *   Instanstiates and maintains the active property on the formElem
  */
  .directive("formElement", ['$compile', function($compile){
    return{
        restrict: "E",
        scope:{formElemModel: '='},
        link: function(scope, element, attrs){
            var template = '';
            var type = scope.formElem.type;
            switch (type){
                case "field":
                    template = 
                        "<form-field-"+scope.formElemModel.fieldType+" ng-switch-when='true'>\
                        </form-field-"+scope.formElemModel.fieldType+">";
                    break;
                default:
                    template = "<form-"+type+" ng-switch-when='true' ></form-"+type+">";
                    break;
            }
            element.html(template);
            $compile(element.contents())(scope);

        // Active state of form Element
        scope.formElem.active = true;
        scope.testActive = function(){
          if(scope.$parent.formElem && scope.$parent.formElem.active == false){
            scope.formElem.active = false;
          }
          else{
            scope.formElem.active = 
              scope.meetsRequirements(scope.formElem.requirements);
          }
        }
        scope.$watch("meetsRequirements(formElem.requirements)", scope.testActive);
        scope.$watch("$parent.formElem.active", scope.testActive);
        }
    }
  }])