我覺得「不從控制器操作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等 –