2016-05-27 126 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