В чем разница между функцией компиляции и компоновки в angularjs

208

Может кто-нибудь объяснить простыми словами?

Документы кажутся немного тупыми. Я не понимаю суть и общую картину того, когда использовать один над другим. Пример, противопоставляющий два, был бы удивительным.

Numan Salati
источник

Ответы:

217
  • функция компиляции - используется для манипулирования DOM шаблона (т. е. манипуляции с элементом tElement = template), следовательно, манипуляции, которые применяются ко всем клонам DOM шаблона, связанным с директивой.

  • Функция link - используется для регистрации слушателей DOM (т. е. выражений $ watch в области видимости экземпляра), а также для манипулирования экземпляром DOM (т. е. манипулирования iElement = индивидуальный элемент экземпляра)
    Это выполняется после того, как шаблон был клонирован. Например, внутри <li ng-repeat ...> функция ссылки выполняется после того, как шаблон <li> (tElement) был клонирован (в iElement) для этого конкретного элемента <li>.
    $ Watch () позволяет директиве получать уведомления об изменениях свойства области экземпляра (область действия экземпляра связана с каждым экземпляром), что позволяет директиве отображать обновленное значение экземпляра в DOM - путем копирования содержимого из области действия экземпляра в ДОМ.

Обратите внимание, что преобразования DOM могут быть выполнены в функции компиляции и / или функции ссылки.

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

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

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

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

Другой способ помочь определить, какой из них использовать: если вы не используете параметр "element" в функции link, то вам, вероятно, не нужна функция link.

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

Обратите внимание, что если вам нужна функция компиляции и функция ссылки (или функции предварительной и последующей ссылки), функция компиляции должна возвращать функцию (и) ссылки, поскольку атрибут '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 (СЕРВИС компиляции уже сканировал директивы для этого элемента).

  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, явно вызывающую СЕРВИС компиляции по третьей директиве (вверх), которая выполняет все три шага в конце.

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

СЦЕНАРИЙ 1: Директива как МАКРО

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

Скажем, у вас есть templateUrl, который указывает на:

<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. СЕРВИС компиляции находит my-field
  2. Он вызывает FUNCTION компиляции в директиве, которая обновляет DOM.
  3. СЕРВИС компиляции затем входит в получившуюся DOM, и COMPILES (рекурсивно)
  4. СЕРВИС компиляции затем вызывает предварительную ссылку сверху вниз
  5. Затем СЕРВИС компиляции вызывает post-link BOTTOM UP, поэтому функция ссылки my-field называется AFTER, внутренние узлы были связаны.

В приведенном выше примере связывание не требуется, поскольку вся работа директивы была выполнена в функции 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 ()), то конечный результат в значительной степени так же, как и раньше (хотя вы можете повторять какую-то работу). Однако, если у элемента были другие директивы, вы просто вызывали их повторную обработку, что может вызывать все виды ошибочного поведения (например, двойная регистрация событий и наблюдений).

Таким образом, фаза компиляции - намного лучший выбор для работы в стиле макросов.

СЦЕНАРИЙ 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>

для автоматического включения правильных директив и элементов div для отображения различных ошибок проверки:

<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>
...

В этом случае вам определенно необходим доступ к области (поскольку именно там хранятся ваши проверки), и вам придется скомпилировать дополнения вручную, опять же будьте осторожны, чтобы не скомпилировать что-либо дважды. (в качестве примечания, вам нужно будет установить имя для тега формы, содержащего форму (здесь я предполагаю theForm), и получить к нему доступ в связи с iElement.parent (). controller ('form'). $ name) ,

В этом случае нет смысла писать функцию компиляции. Ссылка действительно то, что вы хотите. Шаги будут:

  1. Определите шаблон, который полностью лишен угловых директив.
  2. Определите функцию связи, которая добавляет различные атрибуты
  3. УДАЛИТЕ любые угловые директивы, которые вы можете разрешить в элементе верхнего уровня (директива my-field). Они уже обработаны, и это способ предотвратить двойную обработку.
  4. Завершите, вызвав СЕРВИС компиляции для вашего элемента верхнего уровня

Вот так:

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 при повторной компиляции элемента верхнего уровня.

Последнее замечание по этому сценарию: я подразумевал, что вы будете выдавать определение проверок с сервера, и в моем примере я показал их как данные, уже находящиеся в области действия. Я оставляю читателю в качестве упражнения выяснить, как можно справиться с необходимостью извлечения этих данных из REST API (подсказка: отложенная компиляция).

СЦЕНАРИЙ 3: двусторонняя привязка данных по ссылке

Конечно, наиболее распространенное использование ссылки - просто подключить двустороннюю привязку данных через watch / apply. Большинство директив попадают в эту категорию, поэтому они адекватно рассматриваются в других местах.

Тони К.
источник
2
Awsome & Cool Ответ!
Nexus23
Как добавить вложенные элементы без их двойной компиляции?
Art713
50

Из документов:

составитель

Компилятор - это угловой сервис, который пересекает DOM в поисках атрибутов. Процесс компиляции происходит в два этапа.

  1. Компилировать: пройти DOM и собрать все директивы. Результатом является функция связывания.

  2. Ссылка: объедините директивы с областью действия и создайте живое представление. Любые изменения в модели области отражаются в представлении, а любые взаимодействия пользователя с представлением отражаются в модели области. Сделать модель прицела единственным источником правды.

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

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


От @ UmurKontacı :

Если вы собираетесь делать преобразования DOM, так и должно быть compile. Если вы хотите добавить некоторые функции, которые являются изменениями поведения, он должен быть в link.

Мэтт Болл
источник
46
Если вы собираетесь сделать DOMпреобразование, это должно быть, compileесли вы хотите добавить некоторые функции, изменения поведения, это должно быть в link.
Умур Контачи
4
+1 к вышеуказанному комментарию; это самое краткое описание, которое я нашел до сих пор. Это соответствует учебнику, который я нашел здесь .
Бенни Боттема
18

Это из разговора Миско о директивах. http://youtu.be/WqmeI5fZcho?t=16m23s

Думайте о функции компилятора как о том, что работает с шаблоном, и о том, что разрешено изменять сам шаблон, например, добавляя к нему класс или что-то в этом роде. Но это функция связывания, которая фактически выполняет работу по связыванию двух вместе, потому что функция связывания имеет доступ к области действия, и это функция связывания, которая выполняется один раз для каждого экземпляра конкретного шаблона. Таким образом, единственные вещи, которые вы можете поместить в функции компиляции, это вещи, которые являются общими для всех экземпляров.

SunnyShah
источник
10

Немного опоздал с веткой. Но в интересах будущих читателей:

Я наткнулся на следующее видео, которое очень хорошо объясняет компиляцию и линковку в Angular JS:

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

Было бы нежелательно копировать / печатать весь контент здесь. Я сделал пару скриншотов из видео, которые объясняют каждый этап фаз компиляции и линковки:

Компиляция и ссылка в Angular JS

Компиляция и соединение в Angular JS - вложенные директивы

Второй скриншот немного сбивает с толку. Но, если мы будем следовать пошаговой нумерации, это будет довольно просто.

Первый цикл: «Компиляция» выполняется в первую очередь для всех директив.
Второй цикл: «Контроллер» и «Pre-Link» выполняется (только один за другим) Третий цикл: «Post-Link» выполняется в обратном порядке (начиная с самого внутреннего)

Ниже приведен код, который демонстрирует вышеуказанное:

var app = angular.module ('app', []);

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

}]);

app.directive ('message', function ($ interpolate) {
    возвращение{

        compile: function (tElement, tAttributes) { 
            console.log (tAttributes.text + "-In compile ..");
            возвращение {

                pre: function (scope, iElement, iAttributes, controller) {
                    console.log (iAttributes.text + "-In pre ..");
                },

                post: function (область действия, iElement, iAttributes, контроллер) {
                    console.log (iAttributes.text + "-In Post ..");
                }

            }
        },

        controller: function ($ scope, $ element, $ attrs) {
            console.log ($ attrs.text + "-In controller ..");
        },

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

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

ОБНОВИТЬ:

Вторая часть того же видео доступна здесь: https://www.youtube.com/watch?v=1M3LZ1cu7rw. В простом примере видео объясняется, как модифицировать DOM и обрабатывать события во время процесса компиляции и компоновки в Angular JS. ,

user203687
источник
Используется compileи postдля изменения DOM до того, как он будет изменен templateчастично из директивы поставщика.
джедай
6

Две фазы: компиляция и ссылка

Обобщение:

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

Ссылка на сайт:

Когда элемент связан, дерево DOM нарушается в его точке ветвления в дереве DOM, а содержимое заменяется скомпилированным (и связанным) экземпляром шаблона. Исходный смещенный контент либо удаляется, либо, в случае включения, повторно связывается обратно в шаблон. При включении две части соединяются друг с другом (вроде цепочки, с частью шаблона в середине). Когда вызывается функция ссылки, шаблон уже был связан с областью действия и добавлен как дочерний элемент элемента. Функция link - это ваша возможность дополнительно манипулировать DOM и прослушивать изменения настроек.

pixelbits
источник
3

Этот вопрос старый, я хотел бы сделать краткое резюме, которое может помочь:

  • Компиляция вызывается один раз для всех экземпляров директивы
  • Основная цель компиляции - вернуть / создать функцию / объект ссылки (и, возможно, pre / post). Вы также можете инициализировать вещи, которые совместно используются экземплярами директивы.
  • На мой взгляд, «ссылка» - это запутанное название для этой функции. Я бы предпочел «предварительную визуализацию».
  • ссылка вызывается для каждого экземпляра директивы, и ее целью является подготовка рендеринга директивы в DOM.
Кфир Эрез
источник
1
один плюс за название предложения: «pre-render»
Хайлонг Цао