Dyrektywy kątowe - kiedy i jak korzystać z kompilacji, kontrolera, pre-link i post-link [zamknięte]

451

Pisząc dyrektywę Angular, można użyć dowolnej z następujących funkcji do manipulowania zachowaniem DOM, treścią i wyglądem elementu, na którym deklarowana jest dyrektywa:

  • skompilować
  • kontroler
  • link wstępny
  • link do strony

Wydaje się, że istnieje pewne zamieszanie, co do której funkcji należy użyć. To pytanie obejmuje:

Podstawy dyrektywy

Funkcja natury, czynów i nie

Powiązane pytania:

Izhaki
źródło
27
Co do czego ?
haimlit
2
@Ian See: Przeciążenie operatora . Zasadniczo jest to przeznaczone dla wiki społeczności. Zbyt wiele odpowiedzi na powiązane pytania jest częściowe, nie zapewnia pełnego obrazu.
Izhaki,
8
To świetna treść, ale prosimy, aby wszystko tutaj było przechowywane w formacie pytań i odpowiedzi. Być może chciałbyś podzielić to na wiele dyskretnych pytań, a następnie link do nich z tagu wiki?
Flexo
57
Mimo że ten post jest nie na temat i ma formę bloga, był najbardziej użyteczny w zapewnieniu dogłębnego wyjaśnienia dyrektyw Angular. Nie usuwaj tego postu, administratorzy!
Egzegeza
12
Szczerze mówiąc, nawet nie zawracam sobie głowy oryginalnymi dokumentami. Wpis na blogu lub blog zazwyczaj sprawia, że ​​zaczynam działać w ciągu kilku sekund, w przeciwieństwie do 15-30 minut odrywania włosów i próbowania zrozumienia oryginalnych dokumentów.
David

Odpowiedzi:

168

W jakiej kolejności są wykonywane funkcje dyrektywy?

Dla jednej dyrektywy

Na podstawie poniższego upadać , należy rozważyć następujące znaczniki HTML:

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

Z następującą deklaracją dyrektywy:

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

});

Dane wyjściowe konsoli będą:

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

Widzimy, że compilenajpierw jest wykonywany, potem controller, a potem pre-linkostatni post-link.

Dla zagnieżdżonych dyrektyw

Uwaga: Poniższe nie ma zastosowania do dyrektyw, które renderują swoje dzieci w ich funkcji link. Robi to całkiem sporo dyrektyw Angular (takich jak ngIf, ngRepeat lub dowolna dyrektywa z transclude). Dyrektywy te będą natywnie linkwywoływać swoją funkcję przed wywołaniem dyrektyw podrzędnych compile.

Oryginalny znacznik HTML często składa się z zagnieżdżonych elementów, z których każdy ma własną dyrektywę. Tak jak w poniższym znaczniku (patrz paczka ):

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

Dane wyjściowe konsoli będą wyglądać następująco:

// 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)

Możemy tu wyróżnić dwie fazy - fazę kompilacji i fazę łącza .

Faza kompilacji

Gdy DOM jest załadowany, Angular rozpoczyna fazę kompilacji, w której przechodzi od znaczników od góry do dołu i wywołuje compilewszystkie dyrektywy. Graficznie możemy to wyrazić w następujący sposób:

Obraz ilustrujący pętlę kompilacji dla dzieci

Być może warto wspomnieć, że na tym etapie szablony, które otrzymuje funkcja kompilacji, to szablony źródłowe (nie szablony instancji).

Faza łącza

Instancje DOM są często po prostu wynikiem renderowania szablonu źródłowego do DOM, ale mogą być tworzone ng-repeatlub wprowadzane w locie.

Za każdym razem, gdy nowa instancja elementu z dyrektywą jest renderowana do DOM, rozpoczyna się faza połączenia.

W tej fazie, Angular wzywa controller, pre-linkiteruje dzieci i woła post-linkwszystkie dyrektywy, w ten sposób:

Ilustracja przedstawiająca etapy fazy łącza

Izhaki
źródło
5
@lzhaki Schemat blokowy wygląda ładnie. Czy chcesz udostępnić nazwę narzędzia do tworzenia wykresów? :)
merlin
1
@merlin Użyłem OmniGraffle (ale mogłem użyć programu Illustrator lub Inkscape - poza szybkością, OmniGraffle nie ma nic lepszego niż inne narzędzia do tworzenia wykresów, jeśli chodzi o tę ilustrację).
Izhaki,
2
@ Plunker Ananta zniknął, więc oto nowy: plnkr.co/edit/kZZks8HN0iFIY8ZaKJkA?p=preview Otwórz konsolę JS, aby zobaczyć instrukcje dziennika
DLACZEGO nie jest to prawdą, gdy powtórzenie ng jest stosowane w dyrektywach dla dzieci? Zobacz plunk
Luckylooke
@Luckylooke Twój wątek nie ma dzieci z dyrektywą pod ng-repeat (tzn. To, co się powtarza, to szablon z dyrektywą. Gdyby tak było, zobaczyłbyś, że ich kompilacja jest wywoływana tylko po połączeniu ng-repeat.
Izhaki
90

Co jeszcze dzieje się między tymi wywołaniami funkcji?

Poszczególne funkcje dyrektywie są wykonywane od wewnątrz dwóch innych funkcji zwanych kątowych $compile(gdzie dyrektywie compilejest zrealizowanych) oraz wewnętrznych funkcji o nazwie nodeLinkFn(gdzie dyrektywie controller, preLinki postLinksą wykonywane). W funkcji kątowej dzieją się różne rzeczy przed i po wywołaniu funkcji dyrektywy. Być może najbardziej zauważalna jest rekursja dziecka. Poniższa uproszczona ilustracja pokazuje kluczowe kroki w fazie kompilacji i łączenia:

Ilustracja przedstawiająca fazy kompilacji i łączenia Angular

Aby zademonstrować te kroki, użyjmy następującego znacznika HTML:

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

Z następującą dyrektywą:

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

Skompilować

Te compilespojrzenia API tak:

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

Często parametry są poprzedzone przedrostkiem, taby oznaczać, że podane elementy i atrybuty to parametry szablonu źródłowego, a nie instancji.

Przed wywołaniem compiletreści zawartej w transkrypcji (jeśli taka istnieje) jest usuwana, a szablon jest stosowany do znaczników. Zatem element dostarczony do compilefunkcji będzie wyglądał następująco:

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

Zauważ, że treść zawarta w transkrypcji nie jest ponownie wstawiana w tym momencie.

Po wywołaniu dyrektywy .compileAngular przejdzie wszystkie elementy potomne, w tym te, które mogły zostać właśnie wprowadzone przez dyrektywę (na przykład elementy szablonu).

Tworzenie instancji

W naszym przypadku zostaną utworzone (wystąpienia) trzy wystąpienia powyższego szablonu źródłowego ng-repeat. Zatem następująca sekwencja zostanie wykonana trzy razy, raz na instancję.

Kontroler

controllerAPI polega na:

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

Wchodząc w fazę łącza, funkcja łącza zwrócona przez $compilema teraz zakres.

Po pierwsze, funkcja link tworzy na żądanie zasięg potomny ( scope: true) lub zakres izolowany ( scope: {...}).

Następnie wykonywany jest kontroler z zakresem elementu instancji.

Link wstępny

Te pre-linkspojrzenia API tak:

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

Praktycznie nic się nie dzieje między wywołaniem dyrektywy .controllera .preLinkfunkcją. Angular nadal zapewnia zalecenia dotyczące sposobu użycia każdego z nich.

Po .preLinkwywołaniu funkcja link przejdzie przez każdy element potomny - wywołując poprawną funkcję link i dołączając do niej bieżący zakres (który służy jako zakres nadrzędny dla elementów potomnych).

Link do strony

post-linkAPI jest podobna do tej pre-linkfunkcji:

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

Być może warto zauważyć, że po .postLinkwywołaniu funkcji dyrektywy proces łączenia wszystkich jej elementów potomnych został zakończony, w tym wszystkich funkcji dzieci .postLink.

Oznacza to, że do czasu .postLinkwezwania dzieci „żyją” są gotowe. To zawiera:

  • wiązanie danych
  • zastosowane zamknięcie
  • zakres dołączony

Szablon na tym etapie będzie więc wyglądał następująco:

<my-element>
    <div class="ng-binding">
        "{{label}}"
        <div ng-transclude>                
            <div class="ng-scope">Inner content</div>
        </div>
    </div>
</my-element>
Izhaki
źródło
3
Jak stworzyłeś ten rysunek?
Royi Namir,
6
@RoyiNamir Omnigraffle.
Izhaki,
43

Jak zadeklarować różne funkcje?

Kompilacja, kontroler, link wstępny i post-link

Jeśli chcemy korzystać ze wszystkich czterech funkcji, dyrektywa będzie miała następującą postać:

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

Zauważ, że kompilacja zwraca obiekt zawierający zarówno funkcje pre-link, jak i post-link; w Angular lingo mówimy, że funkcja kompilacji zwraca funkcję szablonu .

Kompiluj, kontroluj i przesyłaj pocztą

Jeśli pre-linknie jest to konieczne, funkcja kompilacji może po prostu zwrócić funkcję post-link zamiast obiektu definicji, na przykład:

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

Czasami chce się dodać compilemetodę po linkzdefiniowaniu metody (post) . W tym celu można użyć:

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
        }

    };  
});

Kontroler i łącze post-link

Jeśli żadna funkcja kompilacji nie jest potrzebna, można całkowicie pominąć jej deklarację i podać funkcję post-link w ramach linkwłaściwości obiektu konfiguracyjnego dyrektywy:

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

Brak kontrolera

W dowolnym z powyższych przykładów można po prostu usunąć controllerfunkcję, jeśli nie jest potrzebna. Na przykład, jeśli post-linkpotrzebna jest tylko funkcja, można użyć:

myApp.directive( 'myDirective', function () {
    return {
        restrict: 'EA',
        link: function postLink( scope, element, attributes, controller, transcludeFn ) {
                // Post-link code goes here                 
        },          
    };  
});
Izhaki
źródło
31

Jaka jest różnica między szablonem źródłowym a szablonem wystąpienia ?

Fakt, że Angular pozwala na manipulację DOM, oznacza, że ​​znaczniki wejściowe w procesie kompilacji czasami różnią się od danych wyjściowych. W szczególności niektóre znaczniki wejściowe mogą być klonowane kilka razy (np. Za pomocą ng-repeat) przed ich renderowaniem do DOM.

Terminologia kątowa jest nieco niespójna, ale nadal rozróżnia dwa typy znaczników:

  • Szablon źródłowy - w razie potrzeby znacznik do sklonowania. Po sklonowaniu ten znacznik nie będzie renderowany w DOM.
  • Szablon instancji - rzeczywisty znacznik do renderowania w DOM. Jeśli w grę wchodzi klonowanie, każda instancja będzie klonem.

Poniższy znacznik pokazuje to:

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

Źródłowy HTML określa

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

który służy jako szablon źródłowy.

Ale ponieważ jest zawinięty w ng-repeatdyrektywę, ten szablon źródłowy zostanie sklonowany (w naszym przypadku 3 razy). Te klony są szablonami instancji, każdy pojawi się w DOM i będzie związany z odpowiednim zakresem.

Izhaki
źródło
23

Funkcja kompilacji

compileFunkcja każdej dyrektywy jest wywoływana tylko raz, gdy Angular bootstraps.

Oficjalnie jest to miejsce do wykonywania (źródłowych) manipulacji szablonem, które nie wymagają powiązania zakresu ani danych.

Zasadniczo odbywa się to w celach optymalizacyjnych; rozważ następujące znaczniki:

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

<my-raw>Dyrektywa wykonaniu konkretnego zestawu DOM znaczników. Możemy więc:

  • Pozwól ng-repeatna zduplikowanie szablonu źródłowego ( <my-raw>), a następnie zmodyfikuj znaczniki każdego szablonu instancji (poza compilefunkcją).
  • Zmodyfikuj szablon źródłowy, aby uwzględnić żądany znacznik (w compilefunkcji), a następnie zezwól ng-repeatna jego zduplikowanie.

Jeśli w rawskolekcji znajduje się 1000 przedmiotów , druga opcja może być szybsza niż poprzednia.

Zrobić:

  • Manipuluj znacznikami, aby służył jako szablon dla instancji (klonów).

Nie rób

  • Dołącz programy obsługi zdarzeń.
  • Sprawdź elementy potomne.
  • Skonfiguruj obserwacje atrybutów.
  • Skonfiguruj zegarki na lunecie.
Izhaki
źródło
20

Funkcja kontrolera

controllerFunkcja każdej dyrektywy jest wywoływana za każdym razem, gdy tworzony jest nowy powiązany element.

Oficjalnie controllerfunkcja polega na tym, że:

  • Definiuje logikę (metody) kontrolera, które mogą być współużytkowane przez kontrolery.
  • Inicjuje zmienne zakresu.

Ponownie ważne jest, aby pamiętać, że jeśli dyrektywa obejmuje zakres izolowany, to wszelkie właściwości dziedziczące z zakresu nadrzędnego nie są jeszcze dostępne.

Zrobić:

  • Zdefiniuj logikę kontrolera
  • Zainicjuj zmienne zakresu

Nie rób:

  • Sprawdź elementy potomne (mogą jeszcze nie być renderowane, powiązane z zakresem itp.).
Izhaki
źródło
Cieszę się, że wspomniałeś o kontrolerze w ramach dyrektywy, to świetne miejsce do zainicjowania zakresu. Trudno mi było to odkryć.
jsbisht
1
Kontroler NIE „inicjuje zakresu”, uzyskuje dostęp tylko do zakresu już zainicjowanego niezależnie od niego.
Dmitri Zaitsev,
@DmitriZaitsev dobrą uwagę na szczegóły. Poprawiłem tekst.
Izhaki,
19

Funkcja post-link

Po post-linkwywołaniu funkcji miały miejsce wszystkie poprzednie kroki - powiązanie, zamknięcie itp.

Zazwyczaj jest to miejsce do dalszej manipulacji renderowanym DOM.

Zrobić:

  • Manipuluj elementami DOM (renderowane, a więc tworzone).
  • Dołącz programy obsługi zdarzeń.
  • Sprawdź elementy potomne.
  • Skonfiguruj obserwacje atrybutów.
  • Skonfiguruj zegarki na lunecie.
Izhaki
źródło
9
Jeśli ktoś korzysta z funkcji link (bez linku wstępnego lub post-link), dobrze jest wiedzieć, że jest to odpowiednik linku post-link.
Asaf David,
15

Funkcja wstępnego połączenia

pre-linkFunkcja każdej dyrektywy jest wywoływana za każdym razem, gdy tworzony jest nowy powiązany element.

Jak pokazano wcześniej w sekcji kolejności kompilacji, pre-linkfunkcje są nazywane rodzicem, a następnie dzieckiem, podczas gdy post-linkfunkcje są wywoływane child-then-parent.

pre-linkFunkcja jest rzadko używane, ale mogą być przydatne w szczególnych sytuacjach; na przykład, gdy kontroler podrzędny rejestruje się w kontrolerze nadrzędnym, ale rejestracja musi być parent-then-childmodna ( ngModelControllerrobi to w ten sposób).

Nie rób:

  • Sprawdź elementy potomne (mogą jeszcze nie być renderowane, powiązane z zakresem itp.).
Izhaki
źródło