2016-05-27 75 views
5

我们正在使用AngularJS构建一个大型Web应用程序。 我们针对不同情况使用了自定义指令。当涉及到做DOM操作,绑定事件等......它发生,我们定义的函数,在自定义指令的link函数中操纵DOM,但我们从控制器调用它(我们在$scope中定义函数,以便它可以可由给定的控制器访问)。我认为有角度的做法是为每个函数定义一个单独的自定义指令,并直接从模板中使用它,但在我们的情况下,我不知道它将如何舒适地进行操作,我们已经很多自定义指令,所以它做我们正在做的事情(定义在指令中操作DOM并从控制器调用它的函数),它是否有意义,或者它就像我们正在操纵控制器中的DOM一样?对我们来说,这有点关注分离,我们从来没有定义在控制器中操作DOM的函数,只是在指令中,但是从控制器调用它并不是那么正确,是吗?在angularJS中操作DOM:最佳实践?

展示我们的定制指令如何看起来像一个例子:

angular.module('exp', []).directive('customdirectiveExp', ['', function(){ 
// Runs during compile 
return { 
    name: 'customDirectiveExp', 
    controller: "ControllerExp", 
    controllerAs: "ctrl", 
    templateUrl: 'templateExp', 
    link: function($scope, iElm, iAttrs, controller) { 

     /* These function will be called from the ControllerExp when it needs so. 
     Function can do any things like manipulating the DOM, addin 
     event listner ... 
     */ 
     scope.manipulateDom1 = function(){ 
      // DOM manipualtion 
     }; 

     scope.manipulateDom2 = function(){ 
      // DOM manipualtion 
     }; 

     scope.manipulateDom3 = function(){ 
      // DOM manipualtion 
     }; 

    } 
}; 
}]); 
+0

请包括一个例子 – devqon

+0

@devqon我添加了一个如何编写包含将从控制器调用的函数定义的自定义指令的示例。函数可以做任何事情,从添加事件列表到操纵DOM等 –

回答

7

我觉得“不从控制器操作DOM”的口号是从天,当指令主要/只用于连接功能恢复(或指令控制器,只是一种与其他指令互通的方式)。

当前建议的最佳做法是使用“组件”(可通过​​指令实现),其中基本上所有的指令逻辑均保留在控制器中。 (注意例如在Angular 2中没有链接功能,每个组件/指令基本上是一个类/控制器(加上一些元数据)。)

在这种情况下,我相信这是完全正确的操纵DOM 指令的模板来自指令的控制器。


这个想法是保持你的模板/ HTML声明。比较下面的代码片段:

<!-- 
    `SomeController` reaches out in the DOM and 
    makes changes to `myComponent`'s template --- BAD 
--> 
<div ng-controller="SomeController"> 
    ... 
    <my-component></my-component> 
    ... 
</div> 

VS

<div ng-controller="SomeController"> 
    ... 
    <!-- 
    `myComponent`'s controller makes changes to 
    `myComponent`'s template --- OK 
    --> 
    <my-component></my-component> 
    ... 
</div> 

在第一个(坏)例如,myComponent将根据其中的DOM似乎有不同的行为/外观(例如是SomeController下? )。更重要的是,很难找出其他(不相关的)部分可能会改变myComponent的行为/外观。

在第二个(好的)例子中,myComponent的行为和外观在整个应用程序中将保持一致,并且很容易找出它会是什么:我只需查看指令的定义(一个地方) 。


有几个需要注意的地方,但:

  1. 你不想与其它逻辑混合您的DOM操作代码。 (这会让你的代码更容易维护和更难测试)。

  2. 通常,您希望在后链接阶段操作DOM,当所有的孩子就位(编译+链接)时。在控制器实例化期间运行DOM操作代码将意味着模板内容尚未处理。

  3. 通常,当您的控制器未在指令的上下文中实例化时,您不希望运行DOM操作,因为这意味着您总是需要编译模板才能测试您的控制器。这是不可取的,因为它使得单元测试变得更慢,即使你只想测试与DOM/HTML不相关的控制器逻辑的其他部分。

那么,我们该怎么办?

  • 在专用函数中分离DOM操作代码。这个函数将在适当的时候被调用(见下文),但是所有的DOM交互都在一个地方,这样可以更容易地查看。

  • 将该函数作为控制器方法公开,并从您的指令的链接函数(而不是在控制器初始化期间)调用它。这确保了DOM将处于期望的状态(如果需要的话)并且还从DOM操作中分离出“独立”的控制器实例化。

我们获得了什么:

  • 如果您的控制器实例化指令的编译/连接的一部分,该方法将被调用,而DOM会被操纵,符合市场预期。在单元测试中,如果你不需要DOM操作逻辑,你可以直接实例化控制器并测试它的业务逻辑(独立于任何DOM或编译)。

  • 您可以更好地控制何时发生DOM操作(在单元测试中)。例如。你可以直接实例化控制器,但仍然通过$element,做出你可能想做的任何断言,然后手动调用DOM操作方法并声明元素已正确转换。传递模拟的$element和诸如添加事件监听器之类的东西也更容易,而不必设置真正的DOM。

这种方法(暴露方法,并从链接函数调用它)的缺点是额外的样板。如果您使用的是Angular 1.5.x,那么您可以使用指令控制器生命周期挂钩(例如$onInit$postLink)来省去样板,而无需链接功能,只需掌握控制器并调用其上的方法即可。 (奖金功能:使用带有生命周期挂钩的1.5.x的语法成分,会更容易迁移到角2)

例子:

之前v1.5.x

.directive('myButton', function myButtonDirective() { 
    // DDO 
    return { 
    template: '<button ng-click="$ctrl.onClick()></button>', 
    scope: {} 
    bindToController: { 
     label: '@' 
    } 
    controllerAs: '$ctrl', 
    controller: function MyButtonController($element) { 
     // Variables - Private 
     var self = this; 

     // Functions - Public 
     self._setupElement = _setupElement; 
     self.onClick = onClick; 

     // Functions - Definitions 
     function _setupElement() { 
     $element.text(self.label); 
     } 

     function onClick() { 
     alert('*click*'); 
     } 
    }, 
    link: function myButtonPostLink(scope, elem, attrs, ctrl) { 
     ctrl._setupElement(); 
    } 
    }; 
}) 

v1.5.x后

.component('myButton', { 
    template: '<button ng-click="$ctrl.onClick()></button>', 
    bindings: { 
    label: '@' 
    } 
    controller: function MyButtonController($element) { 
    // Variables - Private 
    var self = this; 

    // Functions - Public 
    self.$postLink = $postLink; 
    self.onClick = onClick; 

    // Functions - Definitions 
    function $postLink() { 
     $element.text(self.label); 
    } 

    function onClick() { 
     alert('*click*'); 
    } 
    } 
}) 
+0

而不是注入'$ element',在MyButtonController里面有一个指令是否有意义?带元素的组件很难测试。 – user2954463

+0

不确定你的意思是“MyButtonController内部的一个指令”:/如果你在'$ postLink'方法(这相当于post-Llink函数)中隔离了DOM接触,它应该不难测试。 – gkalpak

+0

我的意思是,你的组件模板是否有一个指令,然后处理DOM交互?但我现在明白你对后链接的意思。谢谢 – user2954463