Как стать автором
Обновить

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

Время на прочтение 5 мин
Количество просмотров 198K
На мой взгляд, директивы являются основной изюминкой декларативного стиля 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 {
             compile: function compile(temaplateElement, templateAttrs) {
                return {
                    pre: function (scope, element, attrs) {
                    },
                    post: function(scope, element, attrs) { 
                    }
                }
            },
            link: function (scope, element, attrs) {

            },
            priority: 0,
            terminal:false,
            template: '<div></div>',
            templateUrl: 'template.html',
            replace: false,
            transclude: false,
            restrict: 'A',
            scope: false,
            controller: function ($scope, $element, $attrs, $transclude, otherInjectables) {
            }           
        }
    });

Все эти свойства довольно тесно друг с другом связаны и переплетены. И для того, чтобы было проще в этом разобраться, их лучше рассматривать некими смысловыми группами.

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>");                
            },
            link: function (scope, element, attrs) {

            }
        }
    });

Пример обновлен после комментария tamtakoe

В примере выше директива 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';
                    }
                }
            }
        }
    });


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

Публикации

Истории

Работа

Ближайшие события

Московский туристический хакатон
Дата 23 марта – 7 апреля
Место
Москва Онлайн
Геймтон «DatsEdenSpace» от DatsTeam
Дата 5 – 6 апреля
Время 17:00 – 20:00
Место
Онлайн