Кутові директиви - коли і як використовувати компілятор, контролер, попереднє посилання та пост-посилання [закрито]


451

Під час написання кутової директиви можна використовувати будь-яку з наступних функцій для маніпулювання поведінкою, вмістом та виглядом DOM елемента, на який оголошено директиву:

  • складати
  • контролер
  • попереднє посилання
  • пост-посилання

Здається, є деяка плутанина щодо того, яку функцію слід використовувати. Це питання охоплює:

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

Характеристика природи, дії та дії

Пов’язані запитання:


27
Що що?
haimlit

2
@Ian Див.: Перевантаження оператора . По суті, це призначено для вікі спільноти. Занадто багато відповідей на пов'язані з цим питання часткові, не даючи повної картини.
Іжакі

8
Це чудовий зміст, але ми просимо, щоб все тут зберігалось у форматі запитань. Можливо, ви хочете розбити це на кілька дискретних запитань, а потім зв’язатись із ними з wiki тегів?
Flexo

57
Незважаючи на те, що ця публікація є поза темою та у формі блогу, вона була найбільш корисною у наданні поглибленого пояснення кутових директив. Будь ласка, не видаляйте цю публікацію, адміністратори!
Ексегезис

12
Чесно кажучи, я навіть не переймаюся оригінальними документами. Публікація в стацковерсі або блог, як правило, змушує мене йти протягом декількох секунд, проти 15-30 хвилин розривання волосся, намагаючись зрозуміти оригінальні документи.
Девід

Відповіді:


168

У якому порядку виконуються директивні функції?

Для єдиної директиви

На основі наступного планку врахуйте таку розмітку 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і останнє post-link.

Для вкладених директив

Примітка: Далі не поширюється на директиви, які надають своїм дітям функції зв’язку. Досить кілька кутових директив це роблять (наприклад, ngIf, ngRepeat або будь-яка директива з transclude). Ці директиви будуть мати свою linkфункцію раніше, ніж називати їх дочірніми директивами compile.

Оригінальна розмітка HTML часто складається з вкладених елементів, кожен з яких має власну директиву. Як і в наступній розмітці (див. Планк ):

<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всі директиви. Графічно ми можемо виразити це так:

Зображення, що ілюструє цикл складання для дітей

Мабуть, важливо згадати, що на цьому етапі шаблони, які отримує функція компіляції, є вихідними шаблонами (а не шаблоном екземпляра).

Фаза зв’язку

Екземпляри DOM часто є просто результатом надання шаблону джерела в DOM, але вони можуть бути створені ng-repeatабо введені під час руху.

Кожен раз, коли новий екземпляр елемента з директивою надається DOM, починається фаза посилання.

У цій фазі Angular викликає controller, pre-linkповторює дітей та запускає post-linkвсі директиви, як-от так:

Ілюстрація, що демонструє етапи етапу зв'язку


5
@lzhaki Блок-схема виглядає добре. Ви хочете поділитися назвою інструмента для діаграми? :)
merlin

1
@merlin Я використовував OmniGraffle (але міг би використовувати ілюстратор або Inkscape - окрім швидкості, нічого, що стосується цієї графіки, OmniGraffle не робить краще, ніж інші інструменти для графіки).
Іжакі

2
@ Плантант Anant зник, тож ось новий: plnkr.co/edit/kZZks8HN0iFIY8ZaKJkA?p=preview Відкрийте консоль JS, щоб побачити виписки з журналу

ЧОМУ це не відповідає дійсності, коли ng-повтор використовується для дитячих директив ??? Дивіться планк
Luckylooke

@Luckylooke У вашому планку немає дітей з директивою під ng-повтором (тобто, те, що повторюється, є шаблоном із директивою. Якщо це так, ви побачите, що їх компіляція викликається лише після посилання ng-повтору.
Іжакі

90

Що ще відбувається між цими викликами функцій?

Різні функції директив виконані з двох інших в межах кутових функцій , що викликаються $compile(де директива compileвиконуються) і внутрішня функцією , званої nodeLinkFn(де директиви controller, preLinkі postLinkвиконуються). У кутовій функції до і після виклику директивних функцій відбуваються різні речі. Мабуть, найбільш помітною є дитяча рекурсія. Наведена нижче спрощена ілюстрація показує ключові кроки на етапах компіляції та посилання:

Ілюстрація, що показує кутові фази компіляції та зв’язку

Для демонстрації цих кроків скористаємося такою розміткою 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>'
    }
});

Складіть

В compileAPI виглядає наступним чином:

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

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

Перед викликом compileвилучений вміст (якщо такий є) видаляється, а шаблон застосовується до розмітки. Таким чином, елемент, наданий compileфункції, буде виглядати так:

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

Зауважте, що переключений вміст не повторно вставляється в цей момент.

Після заклику до директиви .compileAngular буде проходити через всі дочірні елементи, включаючи ті, які, можливо, тільки що були введені директивою (наприклад, елементи шаблону).

Створення примірників

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

Контролер

controllerAPI включає в себе:

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

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

По-перше, функція посилання створює дочірню область ( scope: true) або ізольований діапазон ( scope: {...}), якщо вимагається.

Потім виконується контролер, забезпечений областю елемента екземпляра.

Попереднє посилання

В pre-linkAPI виглядає наступним чином:

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

Практично нічого не відбувається між викликом директиви .controllerта .preLinkфункцією. Кутова все ще надає рекомендації щодо використання кожного з них.

Після .preLinkвиклику функція зв’язку буде перетинати кожен дочірній елемент - викликаючи правильну функцію посилання та приєднуючи до неї поточну область (що служить батьківською областю для дочірніх елементів).

Посилання на посилання

post-linkAPI аналогічний з 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
Як ви створили цей малюнок?
Royi Namir

6
@RoyiNamir Omnigraffle.
Іжакі

43

Як оголосити різні функції?

Компілювати, контролер, попереднє посилання та посилання

Якщо потрібно використовувати всі чотири функції, директива буде дотримуватися такої форми:

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
                }
            };
        }
    };  
});

Зауважте, що компіляція повертає об'єкт, що містить і функції попереднього посилання, і після посилання; у кутовому мові ми говоримо, що функція компіляції повертає функцію шаблону .

Збірник, контролер та посилання

Якщо 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метод після того, як 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                 
        },          
    };  
});

31

Яка різниця між вихідним шаблоном і шаблоном екземпляра ?

Те, що Angular дозволяє маніпулювати DOM, означає, що розмітка вводу в процес компіляції іноді відрізняється від вихідної. Зокрема, деякі розмітки на вході можуть бути клоновані кілька разів (наприклад, з ng-repeat), перш ніж вони будуть надані до DOM.

Кутова термінологія дещо непослідовна, але вона все ще розрізняє два типи націнок:

  • Вихідний шаблон - розмітка, яку потрібно клонувати, якщо це потрібно. Якщо буде клоновано, ця розмітка не буде надана 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 і буде прив’язаний до відповідної області.


23

Функція компілювання

Кожна compileфункція директиви викликається лише один раз, коли кутові завантажувальні програми.

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

В першу чергу це робиться з метою оптимізації; врахуйте таку розмітку:

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

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

  • Дозволити ng-repeatдублювати вихідний шаблон ( <my-raw>), а потім змінити розмітку кожного шаблону екземпляра (поза compileфункцією).
  • Змініть вихідний шаблон, щоб він включив бажану розмітку (у compileфункцію), а потім дозволить ng-repeatдублювати її.

Якщо в rawsколекції 1000 предметів , останній варіант може бути швидшим, ніж колишній.

Зробіть:

  • Маніпулюйте розміткою, щоб вона слугувала шаблоном для екземплярів (клонів).

Не

  • Приєднайте обробників подій.
  • Огляньте дочірні елементи.
  • Налаштуйте спостереження за атрибутами.
  • Налаштуйте годинник на області застосування.

20

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

controllerФункція кожної директиви викликається кожного разу, коли інстанціюється новий пов'язаний елемент.

Офіційно controllerфункція полягає в тому, де одна:

  • Визначає логіку (методи) контролера, якими можна ділитися між контролерами.
  • Ініціює змінні області застосування.

Знову ж таки, важливо пам’ятати, що якщо директива передбачає ізольований обсяг, будь-які властивості в ній, що успадковуються від батьківського обсягу, ще не доступні.

Зробіть:

  • Визначте логіку контролера
  • Ініціюйте змінні області

Не:

  • Огляньте дочірні елементи (вони можуть бути ще не відображені, обмежені рамками тощо).

Рада, що ви згадали про контролер в директиві - це чудове місце для ініціалізації сфери застосування. Мені було важко це виявити.
jsbisht

1
Контролер НЕ "Ініціює область", він лише отримує доступ до вже розпочатої області, незалежно від неї.
Дмитро Зайцев

@DmitriZaitsev гарну увагу до деталей. Я змінив текст.
Іжакі

19

Функція після посилання

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

Зазвичай це місце для подальшого маніпулювання наданим DOM.

Зробіть:

  • Маніпулюйте елементами DOM (надані та таким чином інстанційними) елементами.
  • Приєднайте обробників подій.
  • Огляньте дочірні елементи.
  • Налаштуйте спостереження за атрибутами.
  • Налаштуйте годинник на області застосування.

9
У випадку, якщо хтось використовує функцію посилання (без попередньої посилання або після посилання), добре знати, що вона еквівалентна пост-посиланню.
Асаф Давид

15

Функція попереднього зв’язку

pre-linkФункція кожної директиви викликається кожного разу, коли інстанціюється новий пов'язаний елемент.

Як було показано раніше в розділі порядку компіляції, pre-linkфункції називаються батьком-тоді-дочірнім, тоді як post-linkфункції називаються child-then-parent.

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

Не:

  • Огляньте дочірні елементи (вони можуть бути ще не відображені, обмежені рамками тощо).
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.