我认为“不要从控制器操作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>
与
<div ng-controller="SomeController"> ... <!-- `myComponent`'s controller makes changes to `myComponent`'s template --- OK --> <my-component></my-component> ...</div>
在第一个(坏的)示例中,
myComponent将具有不同的行为/外观,具体取决于它在DOM中出现的位置(例如,它在
SomeController?下)。更重要的是,很难找出其他(无关)部分可能会更改其
myComponent行为/外观。
在第二个(好的)示例中,
myComponent的行为和外观在整个应用程序中将是一致的,并且很容易找出其含义:我只需要查看指令的定义(一个位置)。
但是有一些警告:
您不想将DOM操作代码与其他逻辑混合使用。(这将使您的代码难以维护且难以测试)。
通常,当所有子项都位于适当位置时(编译+链接),您想在链接后阶段中操作DOM。在控制器实例化期间运行DOM操作代码将意味着尚未处理模板内容。
通常,当您未在指令的上下文中实例化控制器时,您不希望运行DOM操作,因为这意味着您始终需要已编译的模板才能测试控制器。这是不希望的,因为即使您只想测试控制器逻辑中与DOM / HTML不相关的其他部分,它也会使单元测试变慢。
所以,我们能做些什么 ?
在专用功能中隔离DOM操作代码。适当时将调用此函数(请参见下文),但是所有DOM交互都将集中在一个位置,这使得查看起来更加容易。
将该函数公开为控制器方法,然后从指令的链接函数调用它(而不是在控制器初始化期间)。这样可以确保DOM处于所需状态(如果需要),并且还可以将“独立”控制器实例与DOM操作分离。
我们得到的是:
如果将控制器实例化为指令的编译/链接的一部分,则将按预期方式调用该方法并操纵DOM。
在单元测试中,如果不需要DOM操作逻辑,则可以直接实例化控制器并测试其业务逻辑(独立于任何DOM或编译)。
您可以更好地控制DOM操作的发生时间(在单元测试中)。例如,您可以直接实例化控制器,但仍要传递
$element
,进行您可能要进行的任何声明,然后手动调用DOM操作方法并声明元素已正确转换。$element
无需添加真实的DOM,更容易传递诸如添加事件侦听器之类的模拟内容。
这种方法的缺点(公开方法,并从链接函数调用它)是多余的样板。如果您使用的是Angular
1.5.x,则可以通过使用指令控制器生命周期挂钩(例如
$onInit或
$postlink)来节省样板,而无需具有链接功能,只是为了握住控制器并在其上调用方法。(附加功能:将1.5.x组件语法与生命周期挂钩一起使用,将更易于迁移到Angular2。)
例子:
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*'); } }})


