Яка різниця між компіляцією та функцією зв'язку в angularjs


208

Чи може хтось пояснити простими словами?

Документи здаються трохи тупими. Я не отримую сутності та широкої картини, коли використовувати одне над іншим. Приклад, який протиставить це двом, був би приголомшливим.



Відповіді:


217
  • функція компіляції - використання для маніпуляції з DOM шаблону (тобто, маніпулювання tElement = елемент шаблону), отже, маніпуляції, які застосовуються до всіх клонів DOM шаблону, пов'язаних з директивою.

  • функція зв'язку - використання для реєстрації слухачів DOM (тобто, $ watch вирази в області екземпляра), а також маніпуляції DOM екземпляра (тобто, маніпуляція iElement = окремий елемент екземпляра).
    Він виконується після клонування шаблону. Наприклад, усередині <li ng-повторення ...> функція зв'язку виконується після клонування шаблону <li> (tElement) для цього конкретного елемента <li>.
    $ Watch () дозволяє директиві повідомляти про зміни властивостей області екземпляра (область застосування примірника пов'язана з кожним екземпляром), що дозволяє директиві надавати оновлене значення екземпляра до DOM - шляхом копіювання вмісту з області екземпляра в ДОМ.

Зауважте, що перетворення DOM можна проводити у функції компіляції та / або функції зв'язку.

Для більшості директив потрібна лише функція зв'язку, оскільки більшість директив стосується лише конкретного екземпляра елемента DOM (та його області застосування).

Один із способів допомогти визначити, який використовувати: врахуйте, що функція компіляції не отримує scopeаргумент. (Я цілеспрямовано ігнорую аргумент функції зв’язку transclude, який отримує переключену область застосування - це рідко використовується.) Тому функція компіляції не може робити нічого, що ви хотіли б зробити, що вимагає (екземпляр) області - ви можете 't $ дивіться будь-які властивості моделі / екземпляра, ви не можете маніпулювати DOM за допомогою інформації про область екземпляра, ви не можете викликати функції, визначені в області екземпляра тощо

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

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

Інший спосіб допомогти визначити, який використовувати: якщо ви не використовуєте параметр "елемент" у функції посилання, вам, ймовірно, не потрібна функція зв'язку.

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

Зауважте, що якщо вам потрібна функція компіляції та функція зв'язку (або функції до і після посилання), функція компіляції повинна повернути функцію (-и) посилання, оскільки атрибут 'link' ігнорується, якщо атрибут 'compile' визначений.

Дивитися також


5
Найкраще пояснення по компіляції проти посилання.
Nexus23

1
Коли ви говорите, if you don't use the "element" parameter in the link function, then you probably don't need a link function.чи ви маєте на увазі "область" замість "елемент"?
Джейсон Ларке

69

Я бив головою об стіну на цьому пару днів, і відчуваю, що трохи більше пояснень в порядку.

В основному, документи згадують, що розлука значною мірою є підвищенням продуктивності. Я ще раз зазначу, що фаза компіляції використовується в основному тоді, коли вам потрібно змінити DOM до того, як самі елементи будуть складені.

Для наших цілей я наголошу на термінології, яка інакше заплутує:

SERVICE компілятора ($ compile) - це кутовий механізм, який обробляє DOM і виконує різні біти коду в директивах.

ФУНКЦІЯ компіляції - це один біт коду в межах директиви, який запускається в певний час ПО SERVICE компілятора ($ compile).

Деякі зауваження щодо компіляції ФУНКЦІЇ:

  1. Ви не можете змінювати елемент ROOT (той, на який впливає ваша директива), оскільки він вже компілюється із зовнішнього рівня DOM (компіляція SERVICE вже сканувала директиви щодо цього елемента).

  2. Якщо ви хочете додати інші директиви до (вкладених) елементів, виконайте такі дії:

    1. Додавати їх потрібно на етапі компіляції.

    2. Доведеться ввести службу компіляції у фазу зв’язку та скласти елементи вручну. АЛЕ, остерігайтесь складати щось двічі!

Також корисно побачити, як працюють вкладені та явні дзвінки до $ compile, тому я створив майданчик для перегляду цього сайту на http://jsbin.com/imUPAMoV/1/edit . В основному, він просто записує кроки до console.log.

Я повідомлю результати того, що ви побачили в цьому смітнику тут. Для DOM користувацьких директив tp і sp вкладені так:

<tp>
   <sp>
   </sp>
</tp>

Кутова компіляція SERVICE зателефонує:

tp compile
sp compile
tp pre-link
sp pre-link
sp post-link
tp post-link

Код jsbin також має tp post-link FUNCTION, явно викликає компіляцію SERVICE за третьою директивою (вгору), яка виконує всі три етапи в кінці.

Тепер я хочу пройти кілька сценаріїв, щоб показати, як можна було б скористатися компіляцією та посиланням, щоб робити різні речі:

СЦЕНАРІЯ 1: Директива як MACRO

Ви хочете динамічно додати директиву (скажімо, ng-show) до чогось у вашому шаблоні, що ви можете отримати з атрибута.

Скажіть, у вас є шаблонUrl, який вказує на:

<div><span><input type="text"></span><div>

і вам потрібна спеціальна директива:

<my-field model="state" name="address"></my-field>

що перетворює DOM на це:

<div><span ng-show="state.visible.address"><input ng-model="state.fields.address" ...>

в основному, ви хочете зменшити плиту котла, маючи деяку послідовну структуру моделі, яку ваша директива може інтерпретувати. Іншими словами: ви хочете макрос.

Це прекрасне використання для етапу компіляції, оскільки ви можете базувати всі маніпуляції DOM на речах, які ви знаєте лише з атрибутів. Просто додайте атрибути jQuery:

compile: function(tele, tattr) {
   var span = jQuery(tele).find('span').first();
   span.attr('ng-show', tattr.model + ".visible." + tattr.name);
   ...
   return { 
     pre: function() { },
     post: function() {}
   };
}

Послідовність операцій буде (ви можете побачити це через згаданий раніше jsbin):

  1. Компіляція SERVICE знаходить моє поле
  2. Він викликає компіляцію FUNCTION за директивою, яка оновлює DOM.
  3. Потім компільований SERVICE заходить в отриманий DOM і КОМПІЛЮЄ (рекурсивно)
  4. Потім компіляція SERVICE викликає попереднє посилання зверху вниз
  5. Потім компіляція SERVICE викликає післяпосилання BOTTOM UP, тому функція зв'язку мого поля називається ПІСЛЯ внутрішніх вузлів були пов'язані.

У наведеному вище прикладі ніяких зв'язків не потрібно, оскільки вся робота директиви була виконана у компіляції FUNCTION.

У будь-якій точці код директиви може попросити компілятор SERVICE запустити додаткові елементи.

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

directive('d', function($compile) {
  return {
    // REMEMBER, link is called AFTER nested elements have been compiled and linked!
    link: function(scope, iele, iattr) {
      var span = jQuery(iele).find('span').first();
      span.attr('ng-show', iattr.model + ".visible." + iattr.name);
      // CAREFUL! If span had directives on it before
      // you will cause them to be processed again:
      $compile(span)(scope);
    }
});

Якщо ви впевнені, що елементи, які ви переходите до $ compile SERVICE, спочатку були без директив (наприклад, вони походили із визначеного вами шаблону, або ви просто створили їх з angular.element ()), то кінцевий результат досить великий те саме, що і раніше (хоча ви, можливо, повторюєте якусь роботу). Однак, якщо в елементі були інші директиви щодо нього, ви просто змусили їх знову обробитись, що може спричинити різного роду хаотичну поведінку (наприклад, подвійну реєстрацію подій та годинників).

Таким чином, фаза компіляції є набагато кращим вибором для роботи в макро-стилі.

SCENARIO 2: Конфігурація DOM за допомогою даних про область

Це випливає з наведеного вище прикладу. Припустимо, вам потрібен доступ до області, маніпулюючи DOM. Ну, і в цьому випадку розділ компіляції для вас марний, оскільки це відбувається ще до того, як буде доступна область застосування.

Отже, скажімо, ви хочете сутенірувати введення з валідаціями, але ви хочете експортувати валідації з класу ORM на стороні сервера (DRY) та дозволити їх автоматично застосувати та створити відповідний інтерфейс на стороні клієнта для цих перевірок.

Ваша модель може натиснути:

scope.metadata = {
  validations: {
     address: [ {
       pattern: '^[0-9]',
       message: "Address must begin with a number"
     },
     { maxlength: 100,
       message: "Address too long"
     } ]
  }
};
scope.state = {
  address: '123 Fern Dr'
};

і вам може знадобитися директива:

<form name="theForm">
  <my-field model="state" metadata="metadata" name="address">
</form>

автоматично включати відповідні директиви та діви для показу різних помилок перевірки:

<form name="theForm">
  <div>
    <input ng-model="state.address" type="text">
    <div ng-show="theForm.address.$error.pattern">Address must begin with a number</input>
...

У цьому випадку вам обов'язково потрібен доступ до області (оскільки саме там зберігаються ваші перевірки), і вам доведеться збирати доповнення вручну, знову ж таки обережно, щоб не подвійно складати речі. (як бічна примітка, вам потрібно буде вказати ім'я тегу форми, що міститься (я припускаю, що форма тут), і можна отримати доступ до нього у зв’язку з iElement.parent (). контролером ('форма'). $ name) .

У цьому випадку немає сенсу писати функцію компіляції. Посилання - це дійсно те, що ви хочете. Етапи:

  1. Визначте шаблон, повністю позбавлений кутових директив.
  2. Визначте функцію зв’язку, яка додає різні атрибути
  3. ВИДАЛУЙТЕ будь-які кутові директиви, які можна дозволити для елемента верхнього рівня (директива мого поля). Вони вже оброблені, і це спосіб уберегти їх від подвійної обробки.
  4. Закінчіть, зателефонувавши до компіляції SERVICE на елементі верхнього рівня

Так:

angular.module('app', []).
directive('my-field', function($compile) {
  return {
    link: function(scope, iele, iattr) {
      // jquery additions via attr()
      // remove ng attr from top-level iele (to avoid duplicate processing)
      $compile(iele)(scope); // will pick up additions
    }
  };
});

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

Останнє зауваження щодо цього сценарію: я мав на увазі, що ви будете витісняти визначення валідацій із сервера, і в своєму прикладі я показав їх як дані, які вже є в області застосування. Я залишаю це читачею вправою зрозуміти, як можна боротися з необхідністю витягати ці дані з API REST (підказка: відкладена компіляція).

SCENARIO 3: двостороння прив'язка даних через посилання

Звичайно, найпоширенішим використанням посилання є просто підключення двостороннього зв’язування даних за допомогою перегляду / застосування. Більшість директив підпадає під цю категорію, тож вона адекватно охоплена в інших місцях.


2
Чудовий та класний відповідь!
Nexus23

Як додати вкладені елементи без подвійного їх складання?
Art713

50

З документів:

Укладач

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

  1. Складіть: перейдіть DOM і збирайте всі директиви. Результат - функція зв’язування.

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

Деякі директиви такі ng-repeatклонують елементи DOM один раз для кожного елемента колекції. Фаза компіляції та зв’язку покращує продуктивність, оскільки клонований шаблон потрібно зібрати лише один раз, а потім зв'язати один раз для кожного екземпляра клону.

Так що принаймні в деяких випадках дві фази існують окремо як оптимізація.


Від @ UmurKontacı :

Якщо ви збираєтеся робити перетворення DOM, це повинно бути compile. Якщо ви хочете додати деякі функції, що змінюють поведінку, вони повинні бути в link.


46
Якщо ви збираєтеся зробити DOMперетворення, це повинно бути, compileякщо ви хочете додати деякі функції - це зміни поведінки, це повинно бути в link.
Umur Kontacı

4
+1 до коментаря вище; це найкоротший опис, який я знайшов поки що. Це збігається з підручником, який я знайшов тут .
Бенні Боттема

18

Це з розмови Місько про директиви. http://youtu.be/WqmeI5fZcho?t=16m23s

Подумайте про функцію компілятора як про те, що працює над шаблоном, і те, що дозволено змінювати сам шаблон, наприклад, додавши до нього клас чи щось подібне. Але саме функція зв'язування насправді виконує роботу зв’язування обох разом, оскільки функція зв’язування має доступ до сфери застосування, і саме функція сполучення виконується один раз для кожної інстанції конкретного шаблону. Тож єдиний вид речей, який можна розмістити всередині функцій компіляції, - це речі, які є загальними для всіх примірників.


10

Трохи пізно до нитки. Але, на користь майбутніх читачів:

Я натрапив на таке відео, яке дуже чудово пояснює компіляцію та посилання у Angular JS:

https://www.youtube.com/watch?v=bjFqSyddCeA

Не було б приємно копіювати / вводити весь тут вміст. З відео я взяв пару знімків, які пояснюють кожен етап фаз компіляції та зв’язку:

Збір і посилання в кутовій JS

Збір і зв'язок у кутових директивах щодо утворення JS

Другий скріншот трохи заплутаний. Але якщо ми будемо слідувати кроковій нумерації, то це досить прямо.

Перший цикл: "Компілювання" виконується спочатку за всіма директивами.
Другий цикл: "Контролер" та "Попередня посилання" виконуються (лише один за одним) Третій цикл: "Пост-посилання" виконується в зворотному порядку (починаючи з внутрішнього)

Далі йде код, який демонструє вищезазначене:

var app = angular.module ('додаток', []);

app.controller ('msg', ['$ range', функція ($ range) {

}]);

app.directive ('повідомлення', функція ($ інтерполят) {
    повернути {

        компілювати: функція (tElement, tAttributes) { 
            console.log (tAttributes.text + "-У компіляції ..");
            повернути {

                pre: функція (область, iElement, iAttributes, контролер) {
                    console.log (iAttributes.text + "-в попередньому ..");
                },

                post: функція (область, iElement, iAttributes, контролер) {
                    console.log (iAttributes.text + "-In Post ..");
                }

            }
        },

        контролер: функція ($ range, $ елемент, $ attrs) {
            console.log ($ attrs.text + "-контролер ..");
        },

    }
});
<body ng-app="app">
<div ng-controller="msg">
    <div message text="first">
        <div message text="..second">
            <div message text="....third">

            </div>              
        </div>  
    </div>
</div>

ОНОВЛЕННЯ:

Частина 2 цього ж відео доступна тут: https://www.youtube.com/watch?v=1M3LZ1cu7rw Відео пояснює докладніше про те, як змінити DOM та обробляти події під час компіляції та зв’язку процесу Angular JS, у простому прикладі .


Використовується compileі postдля модифікації DOM до того, що він є templateчастиною зміною директиви щодо постачальника.
jedi

6

Дві фази: компілювання та посилання

Збірка:

Обведіть дерево DOM, шукаючи директиви (елементи / атрибути / класи / коментарі). Кожна компіляція директиви може змінювати свій шаблон або змінювати її вміст, який ще не був складений. Після відповідності директиві вона повертає функцію сполучення, яка використовується на більш пізній фазі для з'єднання елементів. В кінці етапу компіляції ми маємо перелік складених директив та їх відповідні функції зв’язку.

Посилання:

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


3

Це питання давнє, я хотів би зробити короткий підсумок, який може допомогти:

  • Компіляція, викликана один раз для всіх екземплярів директиви
  • Основна мета компіляції - повернути / створити посилання (і, можливо, функцію / об'єкт до / після). Ви також можете започаткувати матеріали, які поділяються між екземплярами директиви.
  • На мою думку, "посилання" - це заплутане ім'я для цієї функції. Я вважаю за краще «попередньо віддати».
  • посилання викликається для кожного екземпляра директиви, а його мета - підготувати подання директиви в DOM.

1
один плюс для назви пропозиції: "попередньо віддати"
Хайлун Чао
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.