Директивы в Angularjs для начинающих. Часть 1

от автора

На мой взгляд, директивы являются основной изюминкой декларативного стиля 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';                     }                 }             }         }     }); 

На этом предлагаю сделать паузу. Если тема интересная, в ближайшие дни постараюсь написать продолжение про области видимости и шаблоны.

ссылка на оригинал статьи http://habrahabr.ru/post/179755/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *