На мой взгляд, директивы являются основной изюминкой декларативного стиля Angularjs. Однако, если открыть комментарии пользователей в разделе официальной документации Angularjs, посвященной директивам, то вы увидите, что самый популярный из них: «Пожалуйста, перепишите документацию, сделайте ее более доступной и структурированной. Начинающему разработчику на Angularjs сложно в ней разобраться» («Please rewrite a clearer well structured documentation of directives., this is not friendly to first time angular developers»). С этим сложно не согласится, документация пока еще сыровата и в некоторых моментах приходится прилагать большие усилия, чтобы разобраться в логике и сути функционала. Поэтому я предлагаю вам свой вольный пересказ данной главы в надежде, что кому-то это позволит сэкономить время, а так же рассчитываю на вашу поддержку и участие в комментариях. Итак, поехали!
Как писать директивы?
Директивы в Angularjs задаются вместе с другими конфигурациями модуля следующим образом:
angular.module('moduleName', [])
.directive('directiveName', function () {
/*Метод-фабрика для директивы*/
})
.directive('anotherDirectiveName', function () {
/*Метод-фабрика для директивы*/
});
При этом есть два варианта их объявления. Простой и более мощный длинный варианты.
Простой вариант создания директивы
Для того, чтобы написать директиву, которая будет вызываться при указании в HTML разметке, в простейшем случае вам нужно задать некую функцию (она называется Связующей Linking, но об этом чуть позже), возвращаемую фабрикой:
angular.module('moduleName', [])
.directive('directiveName', function () {
return function(scope,element,attrs){
}
});
Эта функция принимает следующие параметры:
- scope — область видимости, в которой вызывается директива
- element — элемент DOM, которому принадлежит директива, обернутый в jQuery Lite
- attrs — объект со списком всех атрибутов тэга, в котором вызывается директива
Давайте на более развернутом примере. Напишем такую директиву (назовем habra-habr), которая будет складывать две строчки и выводить внутри элемента верстки, в котором вызывается. При этом одну строчку мы будем задавать в качестве переменной контролера(forExampleController), а вторую передавать атрибутом(habra) в этом же тэге. А также оставим за собой возможность определять имя переменной контролера при вызове директивы:
[jsFiddle]
<div ng-app="helloHabrahabr">
<div ng-controller="forExampleController">
<input ng-model="word">
<span habra-habr="word" habra="Nehabra"></span>
</div>
</div>
function forExampleController($scope) {
$scope.word="Habrahabra"
}
angular.module('helloHabrahabr', [])
.directive('habraHabr', function() {
return function($scope, element, attrs) {
/*Задаем функцию, которая будет вызываться при изменении переменной word, ее имя находится в attrs.habraHabr*/
$scope.$watch(attrs.habraHabr,function(value){
element.text(value+attrs.habra);
});
}
});
Всё. Директива в примитивном виде у нас готова. Можно переходить к более развернутой форме её задания.
Развернутый вариант
В своей полноценной форме задание директивы выглядит следующим образом:
angular.module('moduleName', [])
.directive('directiveName', function () {
return {
priority: 0,
template: '<div></div>',
templateUrl: 'template.html',
replace: false,
transclude: false,
restrict: 'A',
scope: false,
controller: function ($scope, $element, $attrs, $transclude, otherInjectables) {
},
compile: function compile(temaplateElement, templateAttrs) {
return {
pre: function (scope, element, attrs) {
},
post: function(scope, element, attrs) {
}
}
},
link: function (scope, element, attrs) {
}
}
});
Все эти свойства довольно тесно друг с другом связаны и переплетены. И для того, чтобы было проще в этом разобраться, их лучше рассматривать некими смысловыми группами.
Link и Compile
Метод Link это та самая функция, которую возвращала фабрика директивы в короткой версии. Здесь надо понять, что в Angularjs процесс компиляции разбит на два этапа:
- compile — анализ всех директив используемых в данном элементе DOM ( в том числе и в его потомках child)
- linking — связывание переменных используемых в шаблоне и переменных в scope
И при этом как в простейшей версии, так и в расширенной метод Link правильно будет называть postLink, поскольку он выполняется после того, как переменные уже сопоставлены. Рассмотрим примеры.
Сперва, я предлагаю переписать пример простой директивы на манер расширенной.
[jsFiddle]
angular.module('helloHabrahabr', [])
.directive('habraHabr', function() {
return {
link:function($scope, element, attrs) {
/*Задаем функцию, которая будет вызываться при изменении переменной word*/
$scope.$watch(attrs.habraHabr,function(value){
element.text(value+attrs.habra);
}
);
}
}
});
То есть всё, действительно, работает по-прежнему. Теперь можно усложнить задачу и сделать так, чтобы наша фраза выводилась не посредством прямого взаимодействия с DOM element.text(...), а внутри директивы interpolate "{{}}":
[jsFiddle]
angular.module('helloHabrahabr', [])
.directive('habraHabrNotwork', function() {
return {
link:function($scope, element, attrs) {
element.html("<div>{{"+attrs.habraHabrWork+"}}"+attrs.habra+"</div>");
}
}
})
.directive('habraHabrWork', function() {
return {
compile: function compile(templateElement, templateAttrs) {
templateElement.html("<div>{{"+templateAttrs.habraHabrWork+"}}"+templateAttrs.habra+"</div>");
return function (scope, element, attrs) {
}
}
}
});
В примере выше директива habraHabrNotwork не будет работать корректно, поскольку мы вставляем директиву "{{}}" с переменными в postLink, то есть, когда уже выполнены компиляця и линкование. Иными словами Angularjs даже не знает, что "{{}}" это директива, которая подлежит исполнению.
Другое дело, вторая директива. Там всё на своем месте, мы вставляем шаблон "{{"+attrs.habraHabrNotwork+"+"+attrs.habra+"}}" до компиляции, и он успешно проходит рендеринг.
Остановимся немного на методе compile. Он может возвращать, как функцию postLink, так и объект с двумя параметрами: pre и post. Где pre и post это методы preLink и postLink соответственно. Из названия методов может показаться, что речь идет о методах до и после Linkа. Но это не совсем так, эти функции выполняются до и после Link а детей директивы в DOM. На примере:
[jsFiddle]
<div ng-app="helloHabrahabr">
<div ng-controller="forExampleController">
<input ng-model="word">
<span habra-habr-work="word" habra="NehabraParent">
<span habra-habr-work="word" habra="NehabraChild"></span>
</span>
<pre>{{log}}</pre>
</div>
</div>
function forExampleController($scope) {
$scope.word="Habrahabra";
$scope.log="";
}
angular.module('helloHabrahabr', [])
.directive('habraHabrWork', function() {
return {
compile: function compile(templateElement, templateAttrs) {
templateElement.prepend("<div>{{"+templateAttrs.habraHabrWork+"}}"+templateAttrs.habra+"</div>");
return {
pre: function ($scope, element, attrs, controller) {
$scope.log+=templateAttrs.habra +' preLink n';
},
post: function ($scope, element, attrs, controller) {
$scope.log+=templateAttrs.habra +' postLink n';
}
}
}
}
});
На этом предлагаю сделать паузу. Если тема интересная, в ближайшие дни постараюсь написать продолжение про области видимости и шаблоны.
Автор: durovchpoknet