Улучшить эту документациюСоздание пользовательских директив
Примечание: это руководство предназначено для разработчиков, уже знакомых с основами AngularJS. Если вы только начинаете, рекомендуем начать с учебника. Если вы ищете API директив, мы недавно переместили его по адресу$compile. В этом документе объясняется, когда следует создавать собственные директивы в вашем приложении AngularJS, и как их реализовать.
Что такое директивы?
В общих чертах, директивы — это метки на элементе DOM (например, атрибут, имя элемента, комментарий или класс CSS), которые сообщают компилятору HTML AngularJS ($compile) о прикреплении к этому элементу DOM определенного поведения (например, через обработчики событий) или даже о преобразовании элемента DOM и его потомков.
В Angular есть набор встроенных директив, таких как ngBind, ngModel, и ngClass. Подобно тому, как вы создаете контроллеры и сервисы, вы можете создавать свои собственные директивы для использования в Angular. Когда Angular инициализирует ваше приложение, компилятор HTML проходит по DOM, сопоставляя директивы с элементами DOM.
Сопоставление директив
Прежде чем мы сможем написать директиву, нам нужно знать, как компилятор HTML Angular определяет, когда использовать заданную директиву.
Аналогично терминологии, используемой при сопоставлении элемента со селектором, мы говорим, что элемент сопоставляется с директивой, когда директива является частью его объявления.
В следующем примере мы говорим, что элемент <input> сопоставляется с директивой ngModel
<input ng-model="foo">
Следующий элемент <input> также сопоставляется с ngModel:
<input data-ng-model="foo">
И следующий элемент person:
<person>{{name}}</person>
Нормализация
Angular нормализует имя тега и атрибута элемента для определения, какие элементы соответствуют каким директивам. Обычно мы ссылаемся на директивы по их чувствительному к регистру имени в формате camelCase (например, ngModel). Однако, поскольку HTML нечувствителен к регистру, мы ссылаемся на директивы в DOM с помощью прописных форм, обычно используя атрибуты с дефисами в элементах DOM (например, ng-model).
Процесс нормализации следующий:
- Удалить
x-иdata-с начала элемента/атрибутов. - Преобразовать имя, разделенное
:,-, или_вcamelCase.
Например, следующие формы эквивалентны и соответствуют директиве ngBind:
ng-bind для ngBind). Если вы хотите использовать инструмент проверки HTML, вы можете вместо этого использовать префикс data (например, data-ng-bind для ngBind). Другие представленные выше формы поддерживаются по причинам совместимости с прошлыми версиями, но мы рекомендуем от них отказаться. Типы директив
$compile может сопоставлять директивы на основе имен элементов, атрибутов, имен классов и комментариев.
Все предоставляемые Angular директивы сопоставляются с именем атрибута, именем тега, комментариями или именем класса. Следующее демонстрирует различные способы ссылки на директиву (myDir в данном случае) в шаблоне:
<my-dir></my-dir> <span my-dir="exp"></span> <!-- directive: my-dir exp --> <span class="my-dir: exp;"></span>
<table>). AngularJS 1.2 представляет ng-repeat-start и ng-repeat-end в качестве лучшего решения для этой проблемы. Разработчикам рекомендуется использовать этот подход вместо пользовательских директив-комментариев, когда это возможно. Создание директив
Сначала давайте поговорим об API для регистрации директив. Как и контроллеры, директивы регистрируются в модулях. Для регистрации директивы используется API module.directive. module.directive принимает нормализованное имя директивы и функцию-фабрику. Эта функция-фабрика должна возвращать объект с различными параметрами, которые сообщают $compile о том, как директива должна вести себя при сопоставлении.
Функция-фабрика вызывается только один раз, когда компилятор сопоставляет директиву с элементом в первый раз. Здесь вы можете выполнить любые инициализационные действия. Функция вызывается с помощью $injector.invoke, что делает ее инъекционной, как и контроллер.
Мы рассмотрим несколько распространенных примеров директив, а затем углубимся в различные параметры и процесс компиляции.
<carousel> , будет проблема, если HTML7 введёт такой же элемент. Префикс из двух или трёх букв (например, btfCarousel) хорошо подойдёт. Аналогично, не префиксруйте свои директивы с ng , иначе они могут конфликтовать с директивами, включёнными в будущие версии Angular. В следующих примерах мы будем использовать префикс my (например, myCustomer).
Директива расширения шаблона
Предположим, у вас есть часть шаблона, представляющая информацию о клиенте. Этот шаблон повторяется многократно в вашем коде. При изменении в одном месте вам нужно изменить его во многих других. Это хороший случай для использования директивы для упрощения шаблона.
Давайте создадим директиву, которая просто заменяет её содержимое статическим шаблоном:
Обратите внимание, что в этой директиве есть привязки. После того, как $compile скомпилирует и свяжет <div my-customer></div>, он попытается сопоставить директивы потомкам элемента. Это означает, что вы можете комбинировать директивы других директив. Мы увидим, как это сделать в примере ниже.
В приведенном выше примере мы встроили значение параметра template, но это станет неудобно по мере роста размера вашего шаблона.
templateUrl.Если вы знакомы с ngInclude, templateUrl работает точно так же. Вот тот же пример, использующий templateUrl вместо:
templateUrl также может быть функцией, которая возвращает URL HTML-шаблона, который нужно загрузить и использовать для директивы. Angular вызовет функцию templateUrl с двумя параметрами: элементом, на котором вызвана директива, и объектом attr , связанным с этим элементом.
templateUrl , так как шаблон запрашивается до инициализации области видимости. restrict.Параметр restrict обычно устанавливается в:
-
'A'— соответствует только имени атрибута -
'E'— соответствует только имени элемента -
'C'— соответствует только имени класса -
'M'— соответствует только комментарию
Эти ограничения можно комбинировать по мере необходимости:
-
'AEC'— соответствует имени атрибута или имени элемента или имени класса
Давайте изменим нашу директиву, чтобы использовать restrict: 'E':
Для получения дополнительной информации о свойстве restrict, см. документацию API.
Использование элемента для директивы myCustomer — это очевидный выбор, потому что вы не добавляете к элементу некоторое «поведение клиента»; вы определяете основное поведение элемента как компонента клиента.
Изоляция области видимости директивы
Наша директива myCustomer выше отлична, но у неё есть существенный недостаток. Мы можем использовать её только один раз в пределах данной области видимости.
В текущей реализации нам пришлось бы создавать отдельный контроллер каждый раз, чтобы повторно использовать такую директиву:
Это явно не очень хорошее решение.
Мы хотим иметь возможность изолировать область видимости внутри директивы от области видимости снаружи, а затем отобразить внешнюю область видимости во внутреннюю область видимости директивы. Мы можем сделать это, создав так называемую изолированную область видимости. Для этого мы можем использовать параметр директивы scope.
Рассматривая index.html, первый элемент <my-customer> связывает атрибут info с naomi, который мы экспонировали в области видимости нашего контроллера. Второй связывает info с igor.
Давайте более подробно рассмотрим опцию области видимости:
//...
scope: {
customerInfo: '=info'
},
//...
Опция области видимости — это объект, содержащий свойство для каждого привязанного к изолированной области видимости. В данном случае у него всего одно свойство:
- Его имя (
customerInfo) соответствует свойству изолированной области видимости директивыcustomerInfo. - Его значение (
=info) указывает$compileна привязку к атрибутуinfo.
=attr атрибуты в опции scope директив нормализуются так же, как и имена директив. Чтобы привязаться к атрибуту в <div bind-to-this="thing">, вы бы указали привязку =bindToThis.В тех случаях, когда имя атрибута совпадает со значением, к которому вы хотите привязаться внутри области видимости директивы, вы можете использовать этот сокращённый синтаксис:
...
scope: {
// same as '=customer'
customer: '='
},
...
Помимо возможности привязывать различные данные к области видимости внутри директивы, использование изолированной области видимости имеет и другой эффект.
Мы можем продемонстрировать это, добавив в нашу область видимости другое свойство vojta и попытавшись получить доступ к нему внутри шаблона нашей директивы:
Обратите внимание, что {{vojta.name}} и {{vojta.address}} пустые, что означает, что они не определены. Хотя мы определили vojta в контроллере, она недоступна внутри директивы.
Как следует из названия, изолированная область видимости директивы изолирует всё, кроме моделей, которые вы явно добавили в объект хэша scope: {}. Это полезно при создании переиспользуемых компонентов, так как это предотвращает изменение состояния модели компонентом, за исключением тех моделей, которые вы явно передали.
scope для создания изолированных областей видимости при создании компонентов, которые вы хотите повторно использовать во всём приложении.Создание директивы, которая манипулирует DOM
В этом примере мы создадим директиву, которая отображает текущее время. Раз в секунду она обновляет DOM, чтобы отразить текущее время.
Директивы, которые хотят изменить DOM, обычно используют опцию link для регистрации слушателей событий DOM и обновления DOM. Она выполняется после клонирования шаблона и здесь будет размещена логика директивы.
link принимает функцию со следующей сигнатурой function link(scope, element, attrs, controller, transcludeFn) { ... }, где:
-
scope— объект области видимости Angular. -
element— обернутый jqLite элемент, которому соответствует эта директива. -
attrs— объект хэша с парами «ключ-значение» нормализованных имён атрибутов и соответствующих значений атрибутов. -
controller— экземпляр(ы) требуемого контроллера директивы или её собственный контроллер (если таковой имеется). Точное значение зависит от свойства require директивы. -
transcludeFn— функция связывания трансклюзии, предварительно связанная с правильной областью видимости трансклюзии.
link обратитесь к странице API $compile API.В нашей функции link мы хотим обновлять отображаемое время раз в секунду или всякий раз, когда пользователь изменяет строку форматирования времени, к которой наша директива привязана. Мы будем использовать сервис $interval для вызова обработчика в соответствии с регулярной периодичностью. Это проще, чем использовать $timeout, но также работает лучше с end-to-end-тестированием, когда мы хотим гарантировать, что все $timeout завершились до завершения теста. Мы также хотим удалить $interval при удалении директивы, чтобы не создавать утечки памяти.
Здесь есть несколько моментов, которые следует отметить. Так же, как и API module.controller, аргумент функции в module.directive внедряется с зависимостью. Благодаря этому мы можем использовать $interval и dateFilter внутри функции link нашей директивы.
Мы регистрируем событие element.on('$destroy', ...). Что вызывает это событие $destroy?
AngularJS генерирует несколько специальных событий. Когда узел DOM, который был скомпилирован с помощью компилятора Angular, уничтожается, он генерирует событие $destroy. Аналогично, когда область видимости AngularJS уничтожается, она транслирует событие $destroy в слушающие области видимости.
Прослушивая это событие, вы можете удалить слушатели событий, которые могут привести к утечкам памяти. Слушатели, зарегистрированные в областях видимости и элементах, автоматически очищаются при их уничтожении, но если вы зарегистрировали слушатель на сервисе или зарегистрировали слушатель на узле DOM, который не удаляется, вам нужно очистить его самостоятельно, чтобы избежать утечки памяти.
element.on('$destroy', ...) или scope.$on('$destroy', ...) для запуска функции очистки при удалении директивы.Создание директивы, которая оборачивает другие элементы
Мы видели, что вы можете передавать модели директиве с помощью изолированной области видимости, но иногда желательно иметь возможность передавать целые шаблоны вместо строки или объекта. Предположим, что мы хотим создать компонент «диалогового окна». Диалоговое окно должно уметь оборачивать любое произвольное содержимое.
Для этого нам нужно использовать опцию transclude.
Что делает эта опция transclude? transclude делает так, что содержимое директивы с этой опцией получает доступ к области видимости вне директивы, а не внутри.
Чтобы проиллюстрировать это, см. пример ниже. Обратите внимание, что мы добавили функцию link в script.js, которая переопределяет name как Jeff. Как, по вашему мнению, будет теперь разрешаться привязка {{name}}?
Обычно мы ожидали бы, что {{name}} будет Jeff. Однако в этом примере мы видим, что привязка {{name}} всё ещё Tobias.
Опция transclude изменяет способ вложенности областей видимости. Она делает так, что содержимое трансклюдированной директивы имеет область видимости вне директивы, а не ту, что внутри. Таким образом, она предоставляет содержимому доступ к внешней области видимости.
Обратите внимание, что если директива не создаёт свою собственную область видимости, то scope в scope.name = 'Jeff' будет ссылаться на внешнюю область видимости, и мы увидим Jeff в выводе.
Это поведение имеет смысл для директивы, которая оборачивает некоторое содержимое, так как в противном случае вам нужно было бы передавать каждую модель, которую вы хотели использовать, отдельно. Если вам нужно передавать каждую модель, которую вы хотите использовать, то вы не можете иметь произвольное содержимое, не так ли?
transclude: true только тогда, когда вы хотите создать директиву, которая оборачивает произвольное содержимое.Далее мы хотим добавить кнопки в это диалоговое окно и предоставить кому-то возможность привязать собственное поведение к нему.
Мы хотим выполнить функцию, которую передаём, вызвав её из области видимости директивы, но запустить её в контексте области видимости, где она зарегистрирована.
Ранее мы видели, как использовать =attr в опции scope, но в вышеприведённом примере мы используем &attr вместо этого. Привязка & позволяет директиве вызывать оценку выражения в контексте исходной области видимости в определённый момент времени. Разрешается любое законное выражение, включая выражение, содержащее вызов функции. Поэтому привязки & идеально подходят для привязки функций обратного вызова к поведению директив.
Когда пользователь нажимает на x в диалоговом окне, вызывается функция close директивы благодаря ng-click. Этот вызов close в изолированной области видимости фактически оценивает выражение hideDialog(message) в контексте исходной области видимости, тем самым выполняя функцию Controller.
Часто желательно передавать данные из изолированной области видимости через выражение в родительскую область видимости. Это можно сделать, передав в функцию-обёртку выражения карту имён локальных переменных и их значений. Например, функция hideDialog принимает сообщение для отображения при скрытии диалогового окна. Это указывается в директиве вызовом close({message: 'closing for now'}) Затем локальная переменная message будет доступна внутри выражения on-close.
&attr в опции scope когда хотите, чтобы ваша директива предоставляла API для привязки к поведению.Создание директивы, которая добавляет слушателей событий
Ранее мы использовали функцию link для создания директивы, которая манипулировала своими DOM-элементами. На основе этого примера давайте создадим директиву, которая реагирует на события на своих элементах.
Например, что если мы хотели создать директиву, которая позволяет пользователю перетаскивать элемент?
Создание общающихся директив
Вы можете комбинировать любые директивы, используя их в шаблонах.
Иногда вам нужен компонент, созданный из комбинации директив.
Представьте, что вы хотите иметь контейнер с вкладками, содержимое которого соответствует активной вкладке.
Директива myPane имеет опцию require со значением ^^myTabs. Когда директива использует эту опцию, $compile выбросит ошибку, если указанный контроллер не найден. Префикс ^^ означает, что эта директива ищет контроллер у своих родителей. (Префикс ^ заставит директиву искать контроллер в собственном элементе или у своих родителей; без префикса директива будет искать только в собственном элементе.)
Итак, откуда берется этот контроллер myTabs? Директивы могут указывать контроллеры, используя опцию controller. Как вы можете видеть, директива myTabs использует эту опцию. Так же, как и ngController, эта опция прикрепляет контроллер к шаблону директивы.
Если необходимо сослаться на контроллер или какие-либо функции, связанные с контроллером, из шаблона, вы можете использовать опцию controllerAs для указания имени контроллера в качестве псевдонима. Директива должна определить область видимости для использования этой конфигурации. Это особенно полезно в случае, когда директива используется как компонент.
Обращаясь к определению myPane, обратите внимание на последний аргумент в его функции link: tabsCtrl. Когда директива требует контроллера, она получает этот контроллер в качестве четвертого аргумента своей функции link. Используя это, myPane может вызвать функцию addPane контроллера myTabs.
Если требуются несколько контроллеров, опция require директивы может принимать аргумент в виде массива. Соответствующий параметр, передаваемый в функцию link, также будет массивом.
angular.module('docsTabsExample', [])
.directive('myPane', function() {
return {
require: ['^^myTabs', 'ngModel'],
restrict: 'E',
transclude: true,
scope: {
title: '@'
},
link: function(scope, element, attrs, controllers) {
var tabsCtrl = controllers[0],
modelCtrl = controllers[1];
tabsCtrl.addPane(scope);
},
templateUrl: 'my-pane.html'
};
});
Внимательные читатели могут задаться вопросом, в чем разница между link и controller. Основное различие заключается в том, что controller может экспонировать API, а функции link могут взаимодействовать с контроллерами, используя require.
controller при необходимости экспонировать API для других директив. В противном случае используйте link. Обзор
Здесь мы рассмотрели основные варианты использования директив. Каждый из этих примеров служит хорошим началом для создания собственных директив.
Вы также можете быть заинтересованы в подробном объяснении процесса компиляции, которое доступно в руководстве по компилятору.
На странице $compile API представлен полный список параметров директив для справки.
© 2010–2017 Google, Inc.
Licensed under the Creative Commons Attribution License 4.0.
https://code.angularjs.org/1.4.14/docs/guide/directive