Использование директив в AngularJS: Теория и практика

| Пятница, 30 августа, 2013

Метки: AngularJS, Javascript Комментарии: 0

Причины разделения процессов компиляции и связывания

После, прочитанного возникает вопрос, почему процесс компиляции разбивается на два шага, на компиляцию и связывание. Чтобы понять это, рассмотрим пример с циклом (ng-repeat), взятый из реального приложения:

Привет {{user}}, ты можешь произвести следующие действия:
<ul>
    <li ng-repeat="action in user.actions">
        {{action.description}}
    </li>
</ul>

Если коротко объяснить, то разделение компиляции и связывания необходимо каждый раз, когда изменения в модели приводят к изменениям в DOM структуре, которая генерируется циклом (ng-repeat).

Когда компилируется выше приведенный пример, компилятор проходит каждый узел в цикле и ищет в нем директивы. Конструкция {{user}} – это пример директивы вставки (interpolation). Но ngRepeat это уже директива совсем другого типа. Эта директива должна штамповать новый элемент li для каждого объекта action в коллекции user.actions. Это значит, что нужно сохранить чистую копию шаблона элемента li для его клонирования, когда вставляются новые объекты actions. То есть шаблон элемента li нужен для клонирования и вставки в элемент ul. Но недостаточно просто клонировать элемент li, еще нужно скомпилировать этот шаблон, чтобы директивы этого шаблона правильно обработались нужным объектом scope (область видимости). Но компиляция каждого элемента li была бы слишком затратной, так как процесс компиляции требует обхода всей структуры DOM для поиска директив и их запуска. Если бы компиляция срабатывала внутри каждого цикла, с коллекцией из 100 объектов, то проблемы с производительностью не заставили бы себя ждать.

Поэтому появилось решение разделить процесс компиляции на две фазы; то есть фаза компиляции, когда все директивы найдены и отсортированы по приоритету и фаза связывания, когда выполняется вся работа по связыванию определенных объектов scope с соответствующими экземплярами конструкций li.

ngRepeat работает, предотвращая процесс компиляции внутри элементов li. Вместо этого ngRepeat компилирует li отдельно. Результатом компиляции элемента li будет являться связующая функция, содержащая все директивы, найденные в элементе li. И эта функция готова прикрепиться к каждому конкретному клону элемента li. Во время выполнения приложения ngRepeat клонирует элемент li, создает для него элемент scope и вызывает связующую функцию для клонированного li.

В итоге:

  • Функция компилирования. Она достаточно редка в самих директивах, поскольку большинство директив в основном работают c экземплярами элементов DOM и редко изменяют структуру этих элементов.
  • Связующая функция. Почти все директивы имеют связующую функцию. Связующая функция позволяет регистрировать специальные объекты listeners(слушатели) для конкретных экземпляров клонированных элементов DOM и заодно копирует содержимое из объекта scope в DOM.

Создание директив (Простой способ)

В примере ниже показано, как создать директиву, которая показывает текущее время:

<!doctype html>
<html ng-app="time">
  <head>
    <script src="http://code.angularjs.org/1.2.0rc1/angular.min.js">
    </script>
    <script src="script.js"></script>
  </head>
  <body>
    <div ng-controller="Ctrl2">
      Формат даты: <input ng-model="format"> <hr/>
      Текущее время: <span my-current-time="format"></span>
    </div>
  </body>
</html>
function Ctrl2($scope) {
  $scope.format = 'M/d/yy h:mm:ss a';
}
 
angular.module('time', [])
  // Регистрируем метод фабрики 'myCurrentTime' для директивы.
  // В качестве параметров передаем сервисы $timeout и dateFilter
  // (применяется внедрение зависимостей).
  .directive('myCurrentTime', function($timeout, dateFilter) {
    // возвращаем связующую функцию директивы. 
    // (функция компиляции не нужна)
    return function(scope, element, attrs) {
      var format,  // формат даты
          timeoutId; // timeoutId, требуется для отмены 
                         // обновления показа времени
 
      // используется для обновления UI
      function updateTime() {
        element.text(dateFilter(new Date(), format));
      }
 
      // наблюдаем за выражением, и обновляем UI при изменении.
      scope.$watch(attrs.myCurrentTime, function(value) {
        format = value;
        updateTime();
      });
 
      // задаем обновление каждую секунду
      function updateLater() {
        // сохраняем timeoutId для отмены
        timeoutId = $timeout(function() {
          updateTime(); // обновляем DOM
          updateLater(); // задаем следующее обновление
        }, 1000);
      }
 
      // слушаем событие удаления элемента
      // и отменяем следующее обновление UI
      element.on('$destroy', function() {
        $timeout.cancel(timeoutId);
      });
 
      updateLater(); // запускаем следущее обновление UI.
    }
  });

Живой пример

Формат даты:
Текущее время:

Комментарии
Никто еще не оставил здесь комментарий.
Войдите, чтобы написать комментарий , или воспользуйтесь формой ниже.
 

Copyright © CodeHint.ru 2013-2024 (v2.4.7 - работает на Angular Universal)Калькулятор инвест-портфеля