- PVSM.RU - https://www.pvsm.ru -
Директивы — это ключевая особенность AngularJS [1]. С помощью директив можно добавлять новое поведение существующим HTML элементам, можно создавать новые компоненты. Примерами директив, добавляющих новое поведения для существующих HTML элементов, могут служить input [2], select [3], textarea [4] в связке с ngModel [5], required и т.п. Перечисленные директивы в основном связаны с валидацией форм [6] в AngularJS. Но тема валидации заслуживает отдельной статьи.
Директивы можно и нужно использовать для повышения модульности вашего приложения, выделения обособленной функциональности в компоненты, в том числе и для повторного использования.
Если вы разрабатываете приложение на AngularJS и не создаете директивы, то это уже само по себе немного настораживает. Либо ваше приложение достаточно простое и уложилось в стандартные возможности AngularJS, либо, скорее всего, что-то не так с архитектурой вашего приложения. А если у вас при этом есть работа с DOM-ом в контроллерах или сервисах, то вам однозначно надо разбираться с темой создания директив, т.к. манипуляций с DOM-ом не должно быть нигде, кроме директив.
В данной статье я постараюсь рассмотреть процесс создания собственных директив на нескольких примерах.
Хорошим примером создания директив могут служить репозитарии команды AngularUI [7]. В эту команду входят разработчики, не являющиеся сотрудниками Google, но очень хорошо зарекомендовавшие себя в списке рассылки [8] и на stackoverflow [9]. Насколько я могу судить, они создают production-ready компоненты с настройками, покрывающими большую часть вариантов использования. У меня тоже есть репозиторий [10], в который я выкладываю некоторые свои наработки. Но у меня немного другой подход. Мне больше нравится делать директивы под конкретные варианты использования. AngularJS очень лаконичен. Меньше кода => лучше читаемость => проще поддержка и изменение. Зачем тогда создавать «монструозные» компоненты с кучей настроек? Поэтому рассматривайте эти директивы как отправную точку для создания своих собственных под конкретные нужды. Еще за примерами можно пойти на сайт ngmodules.org [11], возможно, он сможет стать каталогом различных компонентов для AngularJS.
Итак, базовым документом для разработки своих директив является статья Directives [12] из Developer Guide [13]. Там все расписано очень хорошо и подробно. К этому документу придется возвращаться еще не раз.
Исходный код директивы [14] | Исходный код демо [15] | Демо [16]
angular.module("ExperimentsModule", [])
.directive("tbTooltip", function(){
return function(scope, element, iAttrs) {
iAttrs.$observe('title', function(value) {
element.removeData('tooltip');
element.tooltip();
});
}
});
Использоваться будет примерно так:
<span class="label label-warning"
tb-tooltip
title="You should pay order before {{order.cancelDateTime | date:'dd.MM.yyyy HH:mm'}}"
>
{{order.cancelDateTime | date:'dd.MM.yyyy HH:mm'}}
</span>
В данном примере создается новый модуль [17] ExperimentsModule
. У него нет зависимостей (пустой список зависимостей) — никакие модули не должны быть загружены до него. В этом модуле создается директива tbTooltip
. Директивы при создании всегда именуются с использованием lowerCamelCase. При использовании директиву необходимо именовать в нижнем регистре с использованием в качестве разделителя одного из спец символов: :
, -
, или _
. По желанию для получения валидного кода можно использовать префиксы x-
или data-
. Примеры: tb:tooltip
, tb-tooltip
, tb_tooltip
, x-tb-tooltip
и data-tb-tooltip
.
За названием директивы идет фабричная функция, которая должна вернуть описание директивы. В общем случае описание представляет собой объект, полный список полей которого приведен в документации. Но существует упрощенный вариант, когда можно вернуть только postLink функцию. В этому случае директива в дальнейшем может использоваться только как атрибут какого-либо HTML элемента. В этом примере как раз использован упрощенный вариант создания директивы.
Что такое postLink функция? Когда директива выполняется для конкретного DOM элемента, ее работа состоит из 3-х фаз:
Последовательность выполнения фаз для иерархической структуры наглядно показана здесь [18].
В данном примере осталось еще два ключевых момента.
iAttrs.$observe('title', function(value) { ... })
Как только интерполяция закончена, т.е. получена окончательная текстовая строка, или когда какие-либо данные, участвующие в интерполяции изменились, применяем изменения, используя tooltip компонент Twitter Bootstrap.element.removeData('tooltip');
Исходный код директивы [19] | Исходный код демо [20] | Демо [21]
.directive('uiSource', function () {
return {
restrict: 'EA',
compile: function (elem) {
var escape = function(content) {
return content
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"');
};
var pre = angular.element('<pre class="prettyprint linenums"></pre>');
pre.append(prettyPrintOne(escape(elem.html().slice(1)), undefined, true));
elem.replaceWith(pre);
}
};
});
Использование:
<ui-source>
<ui-source>
<div>
<label>Name:</label>
<input type="text" ng-model="yourName" placeholder="Enter a name here">
<hr>
<h1>Hello {{yourName}}!</h1>
</div>
</ui-source>
</ui-source>
Директива для подсветки кода с использованием google-code-prettify.
Необходимо, чтобы внутреннее содержимое этой директивы не компилировалось и не линковалось, а просто было обработано google-code-prettify.
Данная директива уже реализована через конфигурационный объект. Рассмотрим директиву построчно.
restrict: 'EA',
Директива может использоваться как элемент и как атрибут. В общем случае варианты применения кодируются как 'EACM'. Можно создать директиву, которая может использоваться как элемент 'E', атрибут 'A', класс 'C', комментарий 'M'.
terminal: true,
Означает, что приоритет на котором объявлена эта директива будет последним приоритетом исполнения. Т.е. будут выполнены только директивы приоритетом выше и с таким же. С таким же приоритетом будут выполнены все директивы, т.к. в рамках одного приоритета порядок исполнения директив не определен.
compile: function (elem) {
...
}
На этапе компиляции мы извлекаем содержимое элемента, обрабатываем спецсимволы, заменяя их на мнемоники [22], результат обрабатываем google-code-prettify, обрамляем это все тегом pre и заменяем исходный элемент получившимся.
Вот еще интересные варианты директив, задействующих этап компиляции: ng-if [23], transclude into an attribute [24]. Оставляйте еще примеры в комментариях, добавлю в пост.
Исходный код директивы [25] | Исходный код демо [26] | Демо [27]
Код достаточно длинный, поэтому сюда вставлять не буду.
Использование:
<ui-pagination cur="pagination.cur" total="pagination.total" display="9"></ui-pagination>
Классическая директива с визуальным компонентом.
Ключевая особенность здесь — использование изолированной области видимости (scope).
scope: {
cur: '=',
total: '=',
display: '@'
},
Статья уже получается достаточно большой, поэтому я не буду подробно останавливаться на деталях и всех возможных вариантах. Они хорошо описаны в документации. Кроме того, рекомендую ознакомиться со статьей The Nuances of Scope Prototypal Inheritance [28] (там хорошие визуализации).
В данном случае cur и total будут двунаправлено привязаны через одноименные атрибуты к области видимости, в которой используется директива, а display будет получать обновления через одноименный атрибут из той же области видимости.
Единственное, что хотелось бы отметить: если создается директива с активным использованием NgModelController [29], то, скорее всего, лучше будет использовать не изолированную область видимости (с ней есть определенные проблемы [30]), а новую дочернюю область видимости, объявляемую через scope: true
. Правда при этом в ng-model надо будет указывать свойство объекта (ng-model="pagination.cur"
), а не просто переменную (ng-model="curPage"
). Но просто переменные и не рекомендуется использовать для ng-model (пруф [31], смотрите комментарий Miško Hevery).
Исходный код директивы [32] | Исходный код демо [33] | Демо [34]
Честно говоря, я все откладывал написание этой статьи, пока не напишу подобную директиву :-) Написал статью, смотрю, а она тут уже особо ничего не решает. Но раз уж написана, пусть будет как proof of concept. Можно, конечно, все настройки и через большой объект в атрибуте передавать как в ng-grid [35], но AngularJS может «круче», более декларативно. Поэтому подобный подход, мне кажется, более в духе AngularJS.
Использование:
$scope.data = [
{ column1: 'aaa', column2: '333', column3: 'aaa', column4: 'sdf' },
{ column1: 'bbb', column2: '222', column3: 'zzz', column4: 'sdf' },
{ column1: 'ccc', column2: '111', column3: 'ddd', column4: 'sdf' }
]
<ui-grid source="data">
<column name="column1"></column>
<column name="column2" sortable="true"></column>
<column name="column3" sortable="true"></column>
</ui-grid>
Ключевой момент здесь во взаимодействии директив через контроллер. Вот этот код require: '^uiGrid'
обеспечивает поиск необходимого контроллера на родительских элементах и передает его в link: function (scope, element, attrs, uiGridCtrl) { ... }
.
Статья получилась немаленькая, но я в ней рассмотрел далеко не все. Читайте Developer Guide [36] — он у них хороший и подробный. Вступайте в сообщество в Google+ [37] — лавины постов там нет, но интересные моменты всплывают достаточно часто.
Автор: aav
Источник [38]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/javascript/23917
Ссылки в тексте:
[1] AngularJS: http://angularjs.org/
[2] input: http://docs.angularjs.org/api/ng.directive:input
[3] select: http://docs.angularjs.org/api/ng.directive:select
[4] textarea: http://docs.angularjs.org/api/ng.directive:textarea
[5] ngModel: http://docs.angularjs.org/api/ng.directive:ngModel
[6] валидацией форм: http://docs.angularjs.org/guide/forms
[7] репозитарии команды AngularUI: https://github.com/angular-ui
[8] списке рассылки: https://groups.google.com/forum/?fromgroups#!forum/angular
[9] stackoverflow: http://stackoverflow.com/questions/tagged/angularjs
[10] репозиторий: https://github.com/andreev-artem/angular_experiments
[11] ngmodules.org: http://ngmodules.org/
[12] Directives: http://docs.angularjs.org/guide/directive
[13] Developer Guide: http://docs.angularjs.org/guide/
[14] Исходный код директивы: https://github.com/andreev-artem/angular_experiments/blob/master/tb-tooltip/tb-tooltip.js
[15] Исходный код демо: https://github.com/andreev-artem/angular_experiments/blob/master/tb-tooltip/index.html
[16] Демо: http://andreev-artem.github.com/angular_experiments/tb-tooltip/index.html
[17] модуль: http://docs.angularjs.org/api/angular.module
[18] здесь: http://jsfiddle.net/vojtajina/8yzbZ/
[19] Исходный код директивы: https://github.com/andreev-artem/angular_experiments/blob/master/ui-source/ui-source.js
[20] Исходный код демо: https://github.com/andreev-artem/angular_experiments/blob/master/ui-source/index.html
[21] Демо: http://andreev-artem.github.com/angular_experiments/ui-source/index.html
[22] мнемоники: http://ru.wikipedia.org/wiki/%D0%9C%D0%BD%D0%B5%D0%BC%D0%BE%D0%BD%D0%B8%D0%BA%D0%B8_%D0%B2_HTML
[23] ng-if: https://github.com/andreev-artem/angular_experiments/blob/master/ng-if/ng-if.js
[24] transclude into an attribute: http://stackoverflow.com/q/11703086/457375
[25] Исходный код директивы: https://github.com/andreev-artem/angular_experiments/blob/master/ui-pagination/ui-pagination.js
[26] Исходный код демо: https://github.com/andreev-artem/angular_experiments/blob/master/ui-pagination/index.html
[27] Демо: http://andreev-artem.github.com/angular_experiments/ui-pagination/index.html
[28] The Nuances of Scope Prototypal Inheritance: https://github.com/angular/angular.js/wiki/The-Nuances-of-Scope-Prototypal-Inheritance
[29] NgModelController: http://docs.angularjs.org/api/ng.directive:ngModel.NgModelController
[30] определенные проблемы: http://stackoverflow.com/q/11896732/457375
[31] пруф: https://plus.google.com/118090665492423851447/posts/KKiLKLCF4Xa
[32] Исходный код директивы: https://github.com/andreev-artem/angular_experiments/blob/master/ui-grid/ui-grid.js
[33] Исходный код демо: https://github.com/andreev-artem/angular_experiments/blob/master/ui-grid/index.html
[34] Демо: http://andreev-artem.github.com/angular_experiments/ui-grid/index.html
[35] ng-grid: https://github.com/angular-ui/ng-grid
[36] Developer Guide: http://docs.angularjs.org/guide
[37] сообщество в Google+: https://plus.google.com/communities/115368820700870330756
[38] Источник: http://habrahabr.ru/post/164493/
Нажмите здесь для печати.