【问题标题】:When to write a directive?什么时候写指令?
【发布时间】:2013-03-31 17:01:02
【问题描述】:

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(它只有一个“视图”但没有组件!)。指令在定义组件方面非常出色——但我认为如果你使用它们来创建“视图”,你将失去一些“角度”的方式。不过,这是我的观点——这就是为什么我要征求其他 Angular 社区的想法。

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

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

如果其中一位 Angular 开发人员对此事有意见,那就太棒了!

【问题讨论】:

  • 只要你想进行 DOM 操作,你就需要编写一个指令。控制器中不应该有单一的 do 操作。但是指令里面可以有控制器。
  • 指令控制器有其他一些原因。那些控制器和普通控制器是不同的。

标签: javascript angularjs angularjs-directive


【解决方案1】:

嗯……很好的问题。

我相信指令主要是为了“extending HTML so you can build a DSL”,提高生产力和代码质量。

问题是这是通过组件化来实现的。但最重要我们了解指令不仅与视觉组件有关,也不仅与模板有关,而且还涉及行为

总而言之,使用指令您可以:

  1. 创建 DSL 以增强元素行为
  2. 创建 DSL 小部件,让您不再重复自己
  3. 包装已经存在的组件,提高生产力。
  4. 优化

增强行为只不过是组件化行为。例如,ng-click 将可点击行为添加到任何元素。想象一下,您正在创建一个包含数十个可拖动元素的应用程序。比您创建一个指令来增强元素行为,使其可拖动甚至无需触摸元素视觉 (<span draggable>Test</span>)。再举一个例子,假设您将在鼠标悬停时获得特殊提示。 title 属性不适合这个,那么你可以创建自己的my-title 属性,在鼠标悬停时自动创建你的“特殊提示”(<span my-title="Some caption">Test</span>)。

在开发应用程序时,您有大量特定领域的概念和行为。例如,Stackoverflow 就有很强的投票概念。您可以对问题、答案、cmets 进行上/下投票...因此您可以创建一个可重用的votable 指令,该指令将“投票行为”和“投票小部件”(上/下箭头)添加到几乎任何元素。

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

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

关于包装插件,对于每个 jQuery 插件(但可能是 MooTools、Ext...),您必须创建一个控制器,在其上调用 $('element').plugin(),并关注 jQuery 事件为您更改和 $digest 您的范围。这是指令的另一种完美用法。您可以创建一个指令,在您的元素上应用 .plugin(),监听事件并为您更改/消化您的范围。这就是Angular UI Project 的意义所在,看看吧。

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

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

【讨论】:

    【解决方案2】:

    我个人经常编写指令,因为它们往往使我的程序更具声明性。

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

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

    EDIT - 根据 cmets 的要求,包含一个 sn-p 代码。

      /**
      * 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);
            }
        }
      }])
    

    【讨论】:

    • 所以你基本上是用它来做模板。这是一个用例。如果您可以创建元素级指令(这意味着您不关心 html 验证!),这非常好。您是否考虑过在这种情况下使用 ng-include 而不是指令?当然,自定义命名指令更具语​​义。但只是想知道。也许问题是 - 是否可以对 ng-include 做同样的事情!
    • 在少数情况下(例如简单的 h1)它可能只是模板,但它肯定不在更复杂的元素上,例如使用 select2 加载多个选择的 ajax,以及在JSON :) 确实不可能在 ng-include 中完成大部分工作(在我的情况下),但即使在这种情况下,为什么要这样做?尝试仅使用 html 来完成极其复杂的结构似乎过度使用了模板系统,当然(在 h1 的情况下)我会用对 html 文件的额外调用替换我的一行内联模板,所有这些都带有额外的标题.
    • (double post, sorry) 在我的情况下,ng-include 意味着 a) 15+ html 文件仅用于字段或 b) 加载 ng-switch 指令(尽管它们很有用,但往往变得相当混乱),如果我想要特定的模型行为,或者将函数扔给一个混乱的转储控制器,或者有 10 个以上的控制器,那可能(而且应该)只是指令。 (另外,正如我应该指出的那样,将其全部使用 js 使其非常容易用作小部件) 关于 html 验证,您可以使用类或以数据为前缀的属性来完成所有这些操作- 虽然我必须承认,这是真的,我不在乎:P
    • 你能分享一个你上面提到的指令的代码示例(表单元素)
    • @ganaraj dont care about html validation ...没有理由指令不能都是有效的html。可以使用data- 属性或类来识别指令元素
    猜你喜欢
    • 1970-01-01
    • 2015-02-20
    • 2016-11-06
    • 2021-04-12
    • 2013-10-19
    • 1970-01-01
    • 2011-03-14
    • 1970-01-01
    • 2017-11-18
    相关资源
    最近更新 更多