Angular директивы - когда и как использовать компиляцию, контроллер, предварительную ссылку и пост-ссылку

При написании директивы Angular можно использовать любую из следующих функций для управления поведением DOM, содержимым и внешним видом элемента, на котором объявлена ​​директива:

  • компиляции
  • контроллер
  • предварительно ссылка
  • пост-ссылка

Кажется, есть некоторая путаница в отношении того, какую функцию следует использовать. Этот вопрос охватывает:

Основы директив

Характер функции, do и dont

Похожие вопросы:

Ответ 1

В каком порядке выполняются директивные функции?

Для одной директивы

На основе следующего plunk рассмотрите следующую разметку HTML:

<body>
    <div log='some-div'></div>
</body>

Со следующим объявлением директивы:

myApp.directive('log', function() {

    return {
        controller: function( $scope, $element, $attrs, $transclude ) {
            console.log( $attrs.log + ' (controller)' );
        },
        compile: function compile( tElement, tAttributes ) {
            console.log( tAttributes.log + ' (compile)'  );
            return {
                pre: function preLink( scope, element, attributes ) {
                    console.log( attributes.log + ' (pre-link)'  );
                },
                post: function postLink( scope, element, attributes ) {
                    console.log( attributes.log + ' (post-link)'  );
                }
            };
         }
     };  

});

Выход консоли будет:

some-div (compile)
some-div (controller)
some-div (pre-link)
some-div (post-link)

Мы видим, что сначала выполняется compile, затем controller, затем pre-link и last <<26 > .

Для вложенных директив

Примечание.. Следующее не относится к директивам, которые отображают их дочерние элементы в их функции ссылок. Достаточно несколько директив Angular (например, ngIf, ngRepeat или любая директива с transclude). Эти директивы изначально будут иметь функцию link, вызываемую до вызова их дочерних директив compile.

Оригинальная разметка HTML часто делается из вложенных элементов, каждая со своей собственной директивой. Как в следующей разметке (см. plunk):

<body>
    <div log='parent'>
        <div log='..first-child'></div>
        <div log='..second-child'></div>
    </div>
</body>

Выход консоли будет выглядеть так:

// The compile phase
parent (compile)
..first-child (compile)
..second-child (compile)

// The link phase   
parent (controller)
parent (pre-link)
..first-child (controller)
..first-child (pre-link)
..first-child (post-link)
..second-child (controller)
..second-child (pre-link)
..second-child (post-link)
parent (post-link)

Здесь мы можем различать две фазы: фазу компиляции и фазу связи.

Фаза компиляции

При загрузке DOM Angular начинается фаза компиляции, где она пересекает разметку сверху вниз и вызывает compile для всех директив. Графически мы могли бы выразить это так:

An image illustrating the compilation loop for children

Возможно, важно отметить, что на этом этапе шаблоны, которые получает функция компиляции, являются исходными шаблонами (не шаблон экземпляра).

Фаза связи

экземпляры DOM часто являются просто результатом того, что исходный шаблон отображается в DOM, но они могут быть созданы с помощью ng-repeat или введены "на лету".

Всякий раз, когда новый экземпляр элемента с директивой отображается в DOM, начинается фаза связи.

На этом этапе Angular вызывает controller, pre-link, итерирует дочерние элементы и вызывает post-link для всех директив, например:

An illustration demonstrating the link phase steps

Ответ 2

Что еще происходит между этими вызовами функций?

Различные директивные функции выполняются из двух других angular функций, называемых $compile (где выполняется директива compile) и внутренней функции с именем nodeLinkFn (где директива controller, preLink и postLink). В функции angular происходят различные события до и после вызова директивных функций. Возможно, наиболее важным является рекурсия ребенка. Следующая упрощенная иллюстрация показывает ключевые этапы на этапах компиляции и связи:

An illustration showing Angular compile and link phases

Чтобы продемонстрировать эти шаги, используйте следующую разметку HTML:

<div ng-repeat="i in [0,1,2]">
    <my-element>
        <div>Inner content</div>
    </my-element>
</div>

Со следующей директивой:

myApp.directive( 'myElement', function() {
    return {
        restrict:   'EA',
        transclude: true,
        template:   '<div>{{label}}<div ng-transclude></div></div>'
    }
});

Compile

API compile выглядит так:

compile: function compile( tElement, tAttributes ) { ... }

Часто параметры имеют префикс t, чтобы обозначать элементы и предоставленные атрибуты - это исходный шаблон, а не экземпляр экземпляра.

До вызова compile переданного содержимого (если есть) удаляется, и шаблон применяется к разметке. Таким образом, элемент, предоставляемый функции compile, будет выглядеть так:

<my-element>
    <div>
        "{{label}}"
        <div ng-transclude></div>
    </div>
</my-element>

Обратите внимание на то, что в данный момент переведенное содержимое не будет вставлено повторно.

После вызова директивы .compile, angular будет перемещаться по всем дочерним элементам, включая те, которые, возможно, только что были введены директивой (например, элементы шаблона).

Создание экземпляра

В нашем случае будут созданы три экземпляра исходного шаблона выше (через ng-repeat). Таким образом, следующая последовательность будет выполняться три раза, один раз для каждого экземпляра.

контроллер

API controller включает в себя:

controller: function( $scope, $element, $attrs, $transclude ) { ... }

Ввод фазы ссылки, функция ссылки, возвращаемая через $compile, теперь предоставляется с областью.

Во-первых, функция ссылки создает дочернюю область (scope: true) или изолированную область (scope: {...}), если требуется.

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

Pre-ссылка

API pre-link выглядит так:

function preLink( scope, element, attributes, controller ) { ... }

Практически ничего не происходит между вызовом директивы .controller и .preLink. angular все еще дают рекомендации относительно того, как каждый должен использоваться.

Следуя вызову .preLink, функция link будет пересекать каждый дочерний элемент - вызывая правильную функцию ссылки и прикрепляя к ней текущую область действия (которая служит в качестве родительской области для дочерних элементов).

Пост-ссылка

API post-link аналогичен API-функции pre-link:

function postLink( scope, element, attributes, controller ) { ... }

Возможно, стоит заметить, что как только вызывается директива .postLink, процесс ссылок всех его дочерних элементов завершен, включая все дочерние функции .postLink.

Это означает, что к моменту появления .postLink дети "живут" готовы. Это включает в себя:

  • привязка данных
  • применяется переход на
  • область применения

Шаблон на этом этапе будет выглядеть так:

<my-element>
    <div class="ng-binding">
        "{{label}}"
        <div ng-transclude>                
            <div class="ng-scope">Inner content</div>
        </div>
    </div>
</my-element>

Ответ 3

Как объявить различные функции?

Компиляция, контроль, предварительная ссылка и пост-ссылка

Если использовать все четыре функции, директива будет следовать этой форме:

myApp.directive( 'myDirective', function () {
    return {
        restrict: 'EA',
        controller: function( $scope, $element, $attrs, $transclude ) {
            // Controller code goes here.
        },
        compile: function compile( tElement, tAttributes, transcludeFn ) {
            // Compile code goes here.
            return {
                pre: function preLink( scope, element, attributes, controller, transcludeFn ) {
                    // Pre-link code goes here
                },
                post: function postLink( scope, element, attributes, controller, transcludeFn ) {
                    // Post-link code goes here
                }
            };
        }
    };  
});

Обратите внимание, что компиляция возвращает объект, содержащий как функции предварительной ссылки, так и пост-ссылки; в Angular lingo мы говорим, что функция компиляции возвращает функцию шаблона.

Компиляция, контроль и пост-связь

Если pre-link не требуется, функция компиляции может просто вернуть функцию пост-ссылки вместо объекта определения, например:

myApp.directive( 'myDirective', function () {
    return {
        restrict: 'EA',
        controller: function( $scope, $element, $attrs, $transclude ) {
            // Controller code goes here.
        },
        compile: function compile( tElement, tAttributes, transcludeFn ) {
            // Compile code goes here.
            return function postLink( scope, element, attributes, controller, transcludeFn ) {
                    // Post-link code goes here                 
            };
        }
    };  
});

Иногда рекомендуется добавить метод compile после определения метода (post) link. Для этого можно использовать:

myApp.directive( 'myDirective', function () {
    return {
        restrict: 'EA',
        controller: function( $scope, $element, $attrs, $transclude ) {
            // Controller code goes here.
        },
        compile: function compile( tElement, tAttributes, transcludeFn ) {
            // Compile code goes here.

            return this.link;
        },
        link: function( scope, element, attributes, controller, transcludeFn ) {
            // Post-link code goes here
        }

    };  
});

Контроллер и пост-связь

Если функция компиляции не требуется, можно вообще пропустить ее объявление и предоставить функцию пост-ссылки под свойством link объекта конфигурации директивы:

myApp.directive( 'myDirective', function () {
    return {
        restrict: 'EA',
        controller: function( $scope, $element, $attrs, $transclude ) {
            // Controller code goes here.
        },
        link: function postLink( scope, element, attributes, controller, transcludeFn ) {
                // Post-link code goes here                 
        },          
    };  
});

Нет контроллера

В любом из приведенных выше примеров можно просто удалить функцию controller, если это не требуется. Так, например, если нужна только функция post-link, можно использовать:

myApp.directive( 'myDirective', function () {
    return {
        restrict: 'EA',
        link: function postLink( scope, element, attributes, controller, transcludeFn ) {
                // Post-link code goes here                 
        },          
    };  
});

Ответ 4

В чем разница между исходным шаблоном и шаблоном экземпляра?

Тот факт, что Angular позволяет манипулировать DOM, означает, что разметка ввода в процессе компиляции иногда отличается от вывода. В частности, некоторая вводная разметка может быть клонирована несколько раз (например, с ng-repeat) перед тем, как быть переданной в DOM.

Angular терминология немного противоречива, но она по-прежнему различает два типа разметки:

  • Исходный шаблон - разметка для клонирования, если это необходимо. Если клонировать, эта разметка не будет отображаться в DOM.
  • Шаблон экземпляра - фактическая разметка, которая будет отображаться в DOM. Если задействовано клонирование, каждый экземпляр будет клонировать.

Следующая разметка демонстрирует это:

<div ng-repeat="i in [0,1,2]">
    <my-directive>{{i}}</my-directive>
</div>

Источник html определяет

    <my-directive>{{i}}</my-directive>

который служит исходным шаблоном.

Но поскольку он заключен в директиву ng-repeat, этот шаблон-источник будет клонирован (3 раза в нашем случае). Эти клоны - это шаблон экземпляра, каждый из которых будет отображаться в DOM и связан с соответствующей областью.

Ответ 5

Функция компиляции

Каждая директива compile функция вызывается только один раз, когда Angular загрузочные файлы.

Официально это место для выполнения (исходных) манипуляций с шаблонами, которые не связаны с областью или привязкой данных.

В первую очередь это делается для целей оптимизации; рассмотрите следующую разметку:

<tr ng-repeat="raw in raws">
    <my-raw></my-raw>
</tr>

Директива <my-raw> будет отображать определенный набор разметки DOM. Таким образом, мы можем:

  • Разрешить ng-repeat дублировать исходный шаблон (<my-raw>), а затем модифицировать разметку каждого шаблона экземпляра (вне функции compile).
  • Измените исходный шаблон, чтобы включить требуемую разметку (в функции compile), а затем разрешите ng-repeat дублировать ее.

Если в коллекции raws есть 1000 элементов, последний вариант может быть быстрее первого.

Do

  • Манипулируйте разметку, чтобы она служила шаблоном для экземпляров (клонов).

Не

  • Прикрепить обработчики событий.
  • Осмотреть дочерние элементы.
  • Настроить наблюдения за атрибутами.
  • Настройка часов в области.

Ответ 6

Функция пост-ссылки

Когда вызывается функция post-link, все предыдущие шаги были выполнены - привязка, переключение и т.д.

Это, как правило, место для дальнейшего манипулирования обработанной DOM.

Do

  • Манипулировать DOM (визуализированные и, таким образом, созданные экземпляры) элементы.
  • Прикрепить обработчики событий.
  • Осмотреть дочерние элементы.
  • Настроить наблюдения за атрибутами.
  • Настройка часов в области.

Ответ 7

Функция контроллера

Каждая директива controller функция вызывается всякий раз, когда создается новый связанный элемент.

Официально, функция controller находится там, где одна:

  • Определяет логику (методы) контроллера, которые могут совместно использоваться контроллерами.
  • Инициирует переменные области видимости.

Опять же, важно помнить, что если директива включает изолированную область видимости, любые свойства внутри нее, которые наследуются от родительской области, пока недоступны.

Do

  • Определение логики контроллера
  • Инициировать переменные области

Не выполнять:

  • Осмотреть дочерние элементы (они еще не отображаются, привязаны к области видимости и т.д.).

Ответ 8

Функция предварительной привязки

Каждая директива pre-link функция вызывается всякий раз, когда создается новый связанный элемент.

Как видно ранее в разделе порядка компиляции, функции pre-link называются parent-then-child, тогда как функции post-link называются child-then-parent.

Функция pre-link используется редко, но может быть полезна в специальных сценариях; например, когда дочерний контроллер регистрируется с родительским контроллером, но регистрация должна быть в формате parent-then-child (ngModelController делает все так).

Не выполнять:

  • Осмотреть дочерние элементы (они еще не отображаются, привязаны к области видимости и т.д.).