Jaka jest różnica między funkcją kompilacji a linkiem w angularjs

208

Czy ktoś może wyjaśnić w prosty sposób?

Dokumenty wydają się nieco tępe. Nie dostaję esencji i dużego obrazu tego, kiedy stosować jeden na drugim. Przykład kontrastujący z tymi dwoma byłby niesamowity.

Numan Salati
źródło

Odpowiedzi:

217
  • funkcja kompilacji - służy do manipulacji szablonem DOM (tj. manipulacji elementem tElement = element szablonu), stąd manipulacje, które mają zastosowanie do wszystkich klonów DOM szablonu powiązanych z dyrektywą.

  • funkcja link - służy do rejestrowania detektorów DOM (tj. wyrażeń $ watch w zakresie instancji), a także manipulacji DOM instancji (tj. manipulacji iElement = indywidualny element instancji).
    Jest wykonywany po sklonowaniu szablonu. Np. Wewnątrz <li ng-repeat ...> funkcja link jest wykonywana po sklonowaniu szablonu <li> (tElement) (do iElement) dla tego konkretnego elementu <li>.
    $ Watch () pozwala powiadamiać dyrektywę o zmianach właściwości zasięgu instancji (zasięg instancji jest powiązany z każdą instancją), co pozwala dyrektywie na renderowanie zaktualizowanej wartości instancji do DOM - poprzez kopiowanie treści z zakresu instancji do DOM.

Należy zauważyć, że transformacji DOM można dokonać w funkcji kompilacji i / lub funkcji łączenia.

Większość dyrektyw wymaga tylko funkcji link, ponieważ większość dyrektyw dotyczy tylko określonej instancji elementu DOM (i zakresu jej instancji).

Jeden ze sposobów, aby pomóc określić, którego użyć: rozważ, że funkcja kompilacji nie otrzymuje scopeargumentu. (Celowo ignoruję argument funkcji łączącej transkluzję, który otrzymuje zakryty zakres - jest to rzadko używane). Tak więc funkcja kompilacji nie może zrobić niczego, co chcesz zrobić, co wymaga zakresu (instancji) - możesz nie oglądaj żadnych właściwości zakresu modelu / instancji, nie możesz manipulować DOM przy użyciu informacji o zakresie instancji, nie możesz wywoływać funkcji zdefiniowanych w zakresie instancji itp.

Jednak funkcja kompilacji (podobnie jak funkcja link) ma dostęp do atrybutów. Jeśli więc manipulacje DOM nie wymagają zasięgu instancji, możesz użyć funkcji kompilacji. Oto przykład dyrektywy, która z tych powodów używa tylko funkcji kompilacji. Bada atrybuty, ale nie potrzebuje zasięgu instancji do wykonania swojej pracy.

Oto przykład dyrektywy, która również używa tylko funkcji kompilacji. Dyrektywa musi jedynie przekształcić szablon DOM, aby można było użyć funkcji kompilacji.

Kolejny sposób, aby pomóc określić, którego użyć: jeśli nie użyjesz parametru „element” w funkcji link, prawdopodobnie nie potrzebujesz funkcji link.

Ponieważ większość dyrektyw ma funkcję link, nie podam żadnych przykładów - powinny być bardzo łatwe do znalezienia.

Zauważ, że jeśli potrzebujesz funkcji kompilacji i funkcji link (lub funkcji poprzedzających i post link), funkcja kompilacji musi zwrócić funkcje link, ponieważ atrybut „link” jest ignorowany, jeśli atrybut „compile” jest zdefiniowany.

Zobacz też

Mark Rajcok
źródło
5
Najlepsze wyjaśnienie na temat kompilacji vs linku.
Nexus23
1
Kiedy mówisz, if you don't use the "element" parameter in the link function, then you probably don't need a link function.czy masz na myśli „zakres” zamiast „elementu”?
Jason Larke
69

Przez kilka dni uderzam głową o ścianę i wydaje mi się, że lepiej wyjaśnić trochę więcej.

Zasadniczo, doktorzy wspominają, że separacja jest w dużej mierze poprawą wydajności. Chciałbym powtórzyć, że faza kompilacji jest używana głównie, gdy trzeba zmodyfikować DOM PRZED skompilowaniem samych podelementów.

Dla naszych celów podkreślę terminologię, która w innym przypadku jest myląca:

Usługa kompilatora (kompilacja $) jest mechanizmem kątowym, który przetwarza DOM i uruchamia różne bity kodu w dyrektywach.

FUNKCJA kompilacji to jeden fragment kodu w dyrektywie, który jest uruchamiany w określonym czasie przez USŁUGĘ kompilatora (kompilacja $).

Kilka uwag na temat kompilacji FUNKCJI:

  1. Nie możesz modyfikować elementu ROOT (tego, na który wpływa twoja dyrektywa), ponieważ jest on już kompilowany z poziomu zewnętrznego DOM (kompilacja SERVICE już przeskanowała w poszukiwaniu dyrektyw dotyczących tego elementu).

  2. Jeśli chcesz dodać inne dyrektywy do (zagnieżdżonych) elementów, możesz:

    1. Muszę je dodać podczas fazy kompilacji.

    2. Muszę wstrzyknąć usługę kompilacji do fazy łączenia i ręcznie skompilować elementy. ALE, uważaj na kompilację czegoś dwa razy!

Pomocne jest również sprawdzenie, jak działają zagnieżdżanie i jawne wywołania do kompilacji $, więc stworzyłem plac zabaw do oglądania na http://jsbin.com/imUPAMoV/1/edit . Zasadniczo rejestruje tylko kroki do pliku console.log.

Podam tutaj wyniki tego, co zobaczysz w tym koszu. W przypadku DOM dyrektyw niestandardowych tp i sp zagnieżdżone w następujący sposób:

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

Kompilacja kątowa SERVICE wywoła:

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

Kod jsbin ma również FUNKCJA post-link tp jawnie wywołuje usługę kompilacji SERVICE na trzeciej dyrektywie (w górę), która wykonuje wszystkie trzy kroki na końcu.

Teraz chcę przejść przez kilka scenariuszy, aby pokazać, jak można korzystać z kompilacji i łącza do robienia różnych rzeczy:

SCENARIUSZ 1: Dyrektywa jako MAKRO

Chcesz dynamicznie dodać dyrektywę (powiedzmy ng-show) do czegoś w swoim szablonie, które możesz uzyskać z atrybutu.

Załóżmy, że masz szablonUrl, który wskazuje na:

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

i chcesz niestandardowej dyrektywy:

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

co zmienia DOM w to:

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

w zasadzie chcesz zredukować płytę kotłową, mając pewną spójną strukturę modelu, którą twoja dyrektywa może interpretować. Innymi słowy: chcesz makro.

Jest to świetne zastosowanie w fazie kompilacji, ponieważ możesz oprzeć wszystkie manipulacje DOM na rzeczach, które znasz tylko z atrybutów. Wystarczy użyć jQuery, aby dodać atrybuty:

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

Sekwencja operacji będzie (możesz to zobaczyć za pomocą jsbin wspomnianego wcześniej):

  1. Kompilacja SERVICE znajdzie moje pole
  2. Wywołuje funkcję kompilacji FUNCTION w dyrektywie, która aktualizuje DOM.
  3. Usługa kompilacji następnie przechodzi do wynikowego modelu DOM i KOMPILUJE (rekurencyjnie)
  4. Kompilowana usługa SERVICE następnie wywołuje odgałęzienie pre-link
  5. Kompilowana usługa SERVICE następnie wywołuje post-link BOTTOM UP, więc funkcja link my-field nazywa się PO połączeniu wewnętrznych węzłów.

W powyższym przykładzie nie jest wymagane łączenie, ponieważ cała praca dyrektywy została wykonana w kompilacji FUNKCJA.

W dowolnym momencie kod w dyrektywie może prosić o uruchomienie usługi kompilatora na dodatkowych elementach.

Oznacza to, że możemy zrobić dokładnie to samo w funkcji linku, jeśli wstrzykniesz usługę kompilacji:

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

Jeśli masz pewność, że elementy, które przekazujesz do $ compile SERVICE pierwotnie były wolne od dyrektyw (np. Pochodzą z szablonu, który zdefiniowałeś, lub właśnie utworzyłeś je za pomocą angular.element ()), to wynik końcowy jest prawie całkiem tak samo jak poprzednio (choć możesz powtarzać niektóre prace). Jeśli jednak element zawierał inne dyrektywy, spowodowałeś, że zostały one ponownie przetworzone, co może powodować różnego rodzaju nieprawidłowe zachowania (np. Podwójna rejestracja zdarzeń i zegarków).

Dlatego faza kompilacji jest znacznie lepszym wyborem do pracy w stylu makro.

SCENARIUSZ 2: Konfiguracja DOM za pomocą danych zakresu

Ten wynika z powyższego przykładu. Załóżmy, że potrzebujesz dostępu do zakresu podczas manipulacji DOM. W takim przypadku sekcja kompilacji jest dla ciebie bezużyteczna, ponieważ dzieje się to przed udostępnieniem zakresu.

Powiedzmy, że chcesz ulepszyć dane wejściowe za pomocą walidacji, ale chcesz wyeksportować swoje walidacje z klasy ORM po stronie serwera (DRY), i włączyć je automatycznie i wygenerować odpowiedni interfejs użytkownika po stronie tych walidacji.

Twój model może naciskać:

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

i możesz chcieć dyrektywy:

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

aby automatycznie dołączyć odpowiednie dyrektywy i div, aby pokazać różne błędy sprawdzania poprawności:

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

W takim przypadku zdecydowanie potrzebujesz dostępu do zakresu (ponieważ tam są przechowywane twoje walidacje) i będziesz musiał ręcznie skompilować dodatki, ponownie uważając, aby nie kompilować podwójnie. (na marginesie, musisz ustawić nazwę w tagu zawierającym formularz (zakładam, że tutaj jest formularz) i możesz uzyskać do niego dostęp w połączeniu z iElement.parent (). controller ('form'). $ name) .

W takim przypadku nie ma sensu pisać funkcji kompilacji. Link jest naprawdę tym, czego chcesz. Kroki byłyby następujące:

  1. Zdefiniuj szablon całkowicie pozbawiony dyrektyw kątowych.
  2. Zdefiniuj funkcję łącza, która dodaje różne atrybuty
  3. USUŃ wszelkie dyrektywy kątowe, które możesz dopuścić do elementu najwyższego poziomu (dyrektywa my-field). Zostały już przetworzone i jest to sposób na uniknięcie podwójnego przetwarzania.
  4. Zakończ, wywołując kompilację SERVICE na elemencie najwyższego poziomu

Tak jak:

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

Można oczywiście skompilować zagnieżdżone elementy jeden po drugim, aby uniknąć konieczności martwienia się o powielone przetwarzanie dyrektyw ng podczas ponownej kompilacji elementu najwyższego poziomu.

Ostatnia uwaga na temat tego scenariusza: zasugerowałem, że prześlesz definicję walidacji z serwera, aw moim przykładzie pokazałem je jako dane już w zakresie. Zostawiam to jako ćwiczenie dla czytelnika, aby dowiedzieć się, jak sobie poradzić z koniecznością pobierania tych danych z interfejsu API REST (wskazówka: odroczona kompilacja).

SCENARIUSZ 3: dwukierunkowe wiązanie danych przez łącze

Oczywiście najczęstszym zastosowaniem linku jest po prostu połączenie dwukierunkowego powiązania danych poprzez obejrzenie / zastosowanie. Większość dyrektyw zalicza się do tej kategorii, więc jest ona odpowiednio uwzględniona w innym miejscu.

Tony K.
źródło
2
Super i fajna odpowiedź!
Nexus23,
Jak dodać zagnieżdżone elementy bez podwójnej ich kompilacji?
Art713
50

Z dokumentów:

Kompilator

Kompilator to usługa kątowa, która przemierza DOM w poszukiwaniu atrybutów. Proces kompilacji przebiega w dwóch etapach.

  1. Kompiluj: przejdź przez DOM i zbierz wszystkie dyrektywy. Rezultatem jest funkcja łączenia.

  2. Link: połącz dyrektywy z zakresem i uzyskaj podgląd na żywo. Wszelkie zmiany w modelu zakresu są odzwierciedlane w widoku, a wszelkie interakcje użytkownika z widokiem są odzwierciedlane w modelu zakresu. Zrób model lunety jednym źródłem prawdy.

Niektóre dyrektywy, takie jak ng-repeatklonowanie elementów DOM jeden raz dla każdego elementu w kolekcji. Posiadanie fazy kompilacji i łącza poprawia wydajność, ponieważ sklonowany szablon musi zostać skompilowany tylko raz, a następnie połączony raz dla każdej instancji klonowania.

Tak więc przynajmniej w niektórych przypadkach dwie fazy istnieją osobno jako optymalizacja.


From @ UmurKontacı :

Jeśli zamierzasz dokonać transformacji DOM, tak powinno być compile. Jeśli chcesz dodać niektóre funkcje, które są zmianami zachowania, powinno to być link.

Matt Ball
źródło
46
Jeśli zamierzasz dokonać DOMtransformacji, powinno być, compilejeśli chcesz dodać niektóre funkcje zmiany zachowania, powinno być link.
Umur Kontacı
4
+1 do powyższego komentarza; to najbardziej zwięzły opis, jaki do tej pory znalazłem. Pasuje do samouczka, który tutaj znalazłem .
Benny Bottema
18

To z rozmowy Misko na temat dyrektyw. http://youtu.be/WqmeI5fZcho?t=16m23s

Pomyśl o funkcji kompilatora jako o rzeczy, która działa na szablonie i o tym, że wolno zmieniać sam szablon, na przykład dodając do niego klasę lub coś podobnego. Ale jest to funkcja łączenia, która faktycznie wykonuje połączenie dwóch ze sobą, ponieważ funkcja łączenia ma dostęp do zakresu i jest to funkcja łączenia, która wykonuje się raz dla każdej instancji konkretnego szablonu. Jedynym rodzajem rzeczy, które można umieścić w funkcjach kompilacji, są rzeczy wspólne dla wszystkich instancji.

SunnyShah
źródło
10

Trochę późno na wątek. Ale z korzyścią dla przyszłych czytelników:

Natknąłem się na następujący film, który wyjaśnia Compile and Link w Angular JS w bardzo świetny sposób:

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

Kopiowanie / pisanie wszystkich treści tutaj nie byłoby przyjemne. Z filmu zrobiłem kilka zrzutów ekranu, które wyjaśniają każdy etap faz kompilacji i łączenia:

Kompiluj i łącz w Angular JS

Kompiluj i łącz w Angular JS - zagnieżdżonych dyrektywach

Drugi zrzut ekranu jest nieco mylący. Ale jeśli zastosujemy numerację kroków, jest to całkiem proste.

Pierwszy cykl: „Kompilacja” wykonywana jest najpierw we wszystkich dyrektywach.
Drugi cykl: „Kontroler” i „Łącze wstępne” zostaje wykonany (tylko jeden po drugim) Trzeci cykl: „Łącze postowe” jest wykonywane w odwrotnej kolejności (zaczynając od środka)

Poniżej znajduje się kod, który pokazuje powyższe:

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

app.controller („msg”, [„$ scope”, funkcja ($ scope) {

}]);

app.directive („wiadomość”, funkcja ($ interpolacja) {
    powrót{

        compile: function (tElement, tAttributes) { 
            console.log (tAttributes.text + „-In compile ..”);
            powrót {

                pre: funkcja (zakres, iElement, iAttributes, kontroler) {
                    console.log (iAttributes.text + „-In pre ..”);
                },

                post: funkcja (zakres, iElement, iAttributes, kontroler) {
                    console.log (iAttributes.text + „-In Post ..”);
                }

            }
        },

        kontroler: funkcja ($ zakres, $ 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>

AKTUALIZACJA:

Część 2 tego samego wideo jest dostępna tutaj: https://www.youtube.com/watch?v=1M3LZ1cu7rw Film wideo wyjaśnia więcej na temat modyfikowania DOM i obsługi zdarzeń podczas procesu kompilacji i łączenia Angular JS, w prostym przykładzie .

użytkownik203687
źródło
Używany compilei postmodyfikujący DOM przed jego templateczęściową modyfikacją na podstawie dyrektywy dostawcy.
jedi
6

Dwie fazy: Kompilacja i połączenie

Skompilować:

Przejdź przez drzewo DOM w poszukiwaniu dyrektyw (elementy / atrybuty / klasy / komentarze). Każda kompilacja dyrektywy może modyfikować jej szablon lub modyfikować jej treść, która nie została jeszcze skompilowana. Po dopasowaniu dyrektywa zwraca funkcję łączenia, która jest używana w późniejszej fazie do łączenia ze sobą elementów. Pod koniec fazy kompilacji mamy listę skompilowanych dyrektyw i odpowiadających im funkcji łączenia.

Połączyć:

Kiedy element jest połączony, drzewo DOM jest łamane w punkcie rozgałęzienia w drzewie DOM, a zawartość jest zastępowana skompilowaną (i połączoną) instancją szablonu. Oryginalna wyparta zawartość jest odrzucana lub, w przypadku transkluzji, ponownie powiązana z szablonem. W przypadku transkluzji dwa elementy są z powrotem połączone (coś w rodzaju łańcucha, z kawałkiem szablonu znajdującym się na środku). Po wywołaniu funkcji link szablon został już powiązany z zakresem i dodany jako element potomny elementu. Funkcja linku jest okazją do dalszej manipulacji DOM i nasłuchiwania zmian konfiguracji.

piksele
źródło
3

To pytanie jest stare, chciałbym zrobić krótkie podsumowanie, które może pomóc:

  • Kompilacja wywołana raz dla wszystkich instancji dyrektywy
  • Głównym celem kompilacji jest zwrócenie / utworzenie funkcji / obiektu dowiązania (i ewentualnie pre / post). Możesz także inicjować rzeczy, które są współużytkowane między instancjami dyrektywy.
  • Moim zdaniem „link” to myląca nazwa tej funkcji. Wolałbym „renderowanie wstępne”.
  • link jest wywoływany dla każdej instancji dyrektywy, a jej celem jest przygotowanie renderowania dyrektywy w DOM.
Kfir Erez
źródło
1
jeden plus za nazwę sugestii: „renderowanie wstępne”
Hailong Cao