Управление DOM в angularJS: лучшая практика?

Мы создаем большое веб-приложение, используяAngularJS, Мы часто используем пользовательские директивы для разных случаев. Когда дело доходит до манипулирования DOM, события привязки и т. Д. ... бывает, что мы определяем функции, которые манипулируют DOM в пользовательской директивеlink функция, но мы вызываем ее из контроллера (мы определяем функции в$scope так что это может быть доступно данному контроллеру). Я думаю, что угловой способ сделать это - определить отдельную пользовательскую директиву для каждой функции и использовать ее непосредственно из шаблона, но в нашем случае я не знаю, до какой степени это будет удобно, мы уже много пользовательских директив, поэтому ПЛОХО делать то, что мы делаем (определяя функцию, которая манипулирует DOM в директиве и вызывает ее из контроллера), имеет ли это смысл, или мы манипулируем DOM в контроллере ? Для нас это своего рода разделение интересов, мы никогда не определяем функцию, которая манипулирует 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
        };

    }
};
}]);
 devqon27 мая 2016 г., 12:10
Пожалуйста, включите пример
 Naghmouchi Omar27 мая 2016 г., 14:35
@devqon Я добавил пример того, как мы пишем пользовательскую директиву, которая содержит определение функции, которая будет вызываться из контроллера. Функция может делать любые вещи, от добавления списка событий до манипулирования DOM и т. Д.

Ответы на вопрос(1)

Решение Вопроса

что мантра «не манипулировать 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 с хуками жизненного цикла упростит переход на Angular 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*');
    }
  }
})
 user295446309 авг. 2017 г., 19:07
Я имел в виду, должен ли шаблон вашего компонента содержать директиву, которая затем будет обрабатывать взаимодействие DOM? Но я понимаю, что вы имеете в виду о пост-линке сейчас. Спасибо
 gkalpak09 авг. 2017 г., 18:57
Не уверен, что вы подразумеваете под «директивой внутри MyButtonController»: / Если вы изолируете касание DOM в$postLink метод (который является эквивалентом функции post-Llink), его не должно быть сложно проверить.
 user295446309 авг. 2017 г., 18:08
Вместо инъекций$element, имеет ли смысл иметь директиву внутриMyButtonController? Компоненты с $ element сложнее тестировать.

Ваш ответ на вопрос