2016-05-27 15 views
5

Stiamo costruendo una grande applicazione web usando AngularJS. Utilizziamo una direttiva personalizzata molto per casi diversi. Quando si tratta di manipolazione DOM, evento di binding, ecc. Succede che definiamo funzioni che manipolano il DOM in una funzione della direttiva personalizzata link, ma la chiamiamo dal controller (definiamo le funzioni nello $scope in modo che possa essere accessibile dal controller specificato). Penso che il modo angolare per farlo sarebbe definire una direttiva personalizzata separata per ogni funzione e usarla direttamente dal modello, ma nel nostro caso non so fino a che punto sarà possibile farlo, abbiamo già un sacco di direttive personalizzate, quindi è BAD fare ciò che stiamo facendo (definire una funzione che manipola il DOM in una direttiva e chiamarla dal controller), ha anche un significato o è proprio come stiamo manipolando il DOM nel controller ? Per noi è una questione di separazione, non definiamo mai una funzione che manipoli il DOM nel controller, solo nella direttiva, ma chiamarla dal controller non sembra essere così giusta, vero?Manipolazione del DOM in angularJS: best practice?

Un esempio che mostra come la nostra direttiva personalizzato assomigliare:

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

Si prega di includere un esempio – devqon

+0

@devqon Ho aggiunto un esempio di come scriviamo una direttiva personalizzata che contiene la definizione di funzione che verrà chiamata dal controller.La funzione può fare qualsiasi cosa dall'aggiunta di listener di eventi alla manipolazione del DOM, ecc. –

risposta

7

Credo che il "Non manipolare il DOM dai controller" mantra è tornato dai giorni, quando le direttive principalmente/utilizzati solo funzioni di collegamento (o controllori di direttive in cui solo un modo per comunicare tra loro con altre direttive).

La best practice attualmente suggerita è quella di utilizzare "componenti" (che possono essere realizzati tramite direttive), in cui praticamente tutta la logica direttiva parte nel controller. (Nota ad esempio che in Angular 2 non ci sono funzioni di collegamento e ogni componente/direttiva è fondamentalmente una classe/controller (più alcuni metadati).)

In questo contesto, ritengo che sia perfettamente corretto manipolare il DOM in a della direttiva della direttiva dal della direttiva.


L'idea è di mantenere i vostri modelli/HTML dichiarativi. Confrontare i seguenti frammenti:

<!-- 
    `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> 

Nel primo esempio (cattivo), myComponent avranno diverso comportamento/aspetto a seconda di dove nel DOM sembra (ad esempio è sotto SomeController?). Ciò che è più importante, è molto difficile scoprire quale altra parte (non correlata) potrebbe cambiare il comportamento/aspetto di myComponent.

Nel secondo (buon) esempio, il comportamento e l'aspetto di myComponent saranno coerenti in tutta l'app ed è molto facile scoprire cosa sarà: devo solo cercare nella definizione della direttiva (un posto) .


Ci sono un paio di avvertimenti però:

  1. Non si vuole mescolare il codice di manipolazione del DOM con altra logica. (Renderebbe il tuo codice meno manutenibile e più difficile da testare).

  2. Spesso, si desidera manipolare il DOM nella fase di post-collegamento, quando tutti i bambini sono a posto (compilato + collegato).L'esecuzione del codice di manipolazione DOM durante l'istanziazione del controller significherebbe che il contenuto del modello non è ancora stato elaborato.

  3. In genere, non si desidera eseguire la manipolazione DOM quando il controller non viene istanziato nel contesto di una direttiva, poiché ciò significherebbe che è sempre necessario un modello compilato per testare il controller. Ciò non è desiderabile, perché rende più lenti i test delle unità, anche se si desidera testare solo altre parti della logica del controller che non sono correlate a DOM/HTML.

Quindi, cosa possiamo fare?

  • Isolare il codice di manipolazione DOM in una funzione dedicata. Questa funzione verrà chiamata quando appropriato (vedi sotto), ma tutte le interazioni DOM saranno in un unico posto, il che rende più facile la revisione.

  • Esporre questa funzione come metodo di controllo e chiamarla dalla funzione di collegamento della direttiva (anziché durante l'inizializzazione del controller). Ciò garantisce che il DOM si trovi nello stato desiderato (se necessario) e disaccoppia anche l'istanza del controller "stand-alone" dalla manipolazione del DOM.

Cosa ci guadagno:

  • Se il controller viene istanziato come parte della direttiva compilazione/linking, il metodo sarà chiamato e il DOM verrà manipolato, come previsto.

  • Nei test di unità, se non è necessaria la logica di manipolazione DOM, è possibile creare un'istanza diretta del controllore e testarne la logica di business (indipendentemente da qualsiasi DOM o compilazione).

  • Hai più controllo su quando avviene la manipolazione del DOM (in unit test). Per esempio. puoi istanziare direttamente il controller, ma comunque passare in un $element, fare tutte le asserzioni che potresti voler fare, quindi chiamare manualmente il metodo di manipolazione del DOM e asserire che l'elemento è stato trasformato correttamente. È anche più facile passare in un $element deriso e cose come aggiungere listener di eventi, senza dover configurare un vero DOM.

Lo svantaggio di questo approccio (esponendo il metodo e chiamandolo dalla funzione di collegamento), è la piastra supplementare. Se si utilizza Angular 1.5.x, è possibile risparmiare il boilerplate utilizzando i ganci del ciclo di vita del controllore direttivo (ad esempio $onInit o $postLink), senza la necessità di disporre di una funzione di collegamento, solo per ottenere il controller e richiamare un metodo su di esso . (caratteristica bonus: Utilizzando la sintassi componente 1.5.x con ganci ciclo di vita, faciliterebbe a migrare verso angolari 2.)

Esempi:

Prima 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(); 
    } 
    }; 
}) 

Dopo 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

Invece di iniettare '$ element', ha senso avere una direttiva all'interno di' MyButtonController'? I componenti con $ element sono più difficili da testare. – user2954463

+0

Non sai cosa intendi con "una direttiva in MyButtonController":/Se si isola il DOM toccando il metodo '$ postLink' (che è l'equivalente della funzione post-Llink), non dovrebbe essere difficile da testare. – gkalpak

+0

Intendevo, se il modello del componente conteneva una direttiva che gestisse l'interazione DOM? Ma capisco cosa intendi per post-link ora. Grazie – user2954463