我觉得“不从控制器操作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
的行为和外观在整个应用程序中将保持一致,并且很容易找出它会是什么:我只需查看指令的定义(一个地方) 。
有几个需要注意的地方,但:
你不想与其它逻辑混合您的DOM操作代码。 (这会让你的代码更容易维护和更难测试)。
通常,您希望在后链接阶段操作DOM,当所有的孩子就位(编译+链接)时。在控制器实例化期间运行DOM操作代码将意味着模板内容尚未处理。
通常,当您的控制器未在指令的上下文中实例化时,您不希望运行DOM操作,因为这意味着您总是需要编译模板才能测试您的控制器。这是不可取的,因为它使得单元测试变得更慢,即使你只想测试与DOM/HTML不相关的控制器逻辑的其他部分。
那么,我们该怎么办?
我们获得了什么:
如果您的控制器实例化指令的编译/连接的一部分,该方法将被调用,而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*');
}
}
})
请包括一个例子 – devqon
@devqon我添加了一个如何编写包含将从控制器调用的函数定义的自定义指令的示例。函数可以做任何事情,从添加事件列表到操纵DOM等 –