AngularJS #7 – HTML na sterydach – wprowadzenie do dyrektyw

AngularJS



AngularJS nie byłby bez dyrektyw tym samym frameworkiem. To właśnie dyrektywy są absolutną przewagą AngularJS. Tak jak w tytule – użycie dyrektyw, to HTML na sterydach. Dzięki nim jesteśmy w stanie nauczyć nasz HTML nowych sztuczek.

Dyrektywa

Czym jest więc wspomniana dyrektywa? W poprzednich wpisach kilka razy wspominałem o dodatkowych elementach, atrybutach, komentarzach HTML oraz klasach CSS, które możemy używać w HTML dzięki wykorzystaniu AngularJS. Te dodatkowe rzeczy, to nic innego jak dyrektywy. AngularJS w fazie kompilacji ($compile) skanuje drzewo DOM naszego dokumentu HTML, a następnie w miejsce wystąpień dyrektyw podpina funkcjonalności na poziomie samego DOM. Owe funkcjonalności mają zastosowanie na poziomie elementu, dla którego zdefiniowaliśmy dyrektywę oraz wszystkich jego dzieci. Dyrektywy pozwalają w ten sposób budować reużywalne komponenty, które pozwalają manipulować drzewem DOM oraz dostarczać mu nowych funkcjonalności.

Przykłady istniejących dyrektyw znajdziecie praktycznie w każdym miejscu. ng-appng-controllerng-bind istnieją w AngularJS. W poprzednich wpisach wykorzystałem chociażby dyrektywę ng-repeat, która pozwoliła mi zbudować dynamiczną listę w oparciu o kolekcję obiektów. Dyrektywy są najczęściej wykorzystywanym elementem AngularJS i stanowią o jego sile i przewadze nad innymi frameworkami JavaScript.

Opcje

Dyrektywy tworzone mogą być na kilka różnych sposobów. Posiadają także szereg parametrów konfiguracyjnych, które odpowiadają za samo funkcjonowanie oraz użycie dyrektyw. Przejdźmy przez większość z nich.

restrict

Jak już wcześniej wspomniałem – dyrektywy możemy wywoływać w HTML na kilka sposobów. Określa to parametr restrict, który przyjmuje wartości:

restrict: 'A'
<div my-directive></div>
restrict: 'C'
<div class="my-directive: expression;"></div>
restrict: 'E'
<my-directive></my-directive>
restrict: 'M'
<!-- directive: my-directive expression -->

Możemy oczywiście podać wszystkie wartości jednocześnie: restrict: ‘EMAC’. Najbardziej popularne i chyba najrozsądniejsze jest wykorzystanie atrybutów oraz elementów HTML jako sposobu wywołań dyrektyw. Najlepiej radzą sobie z tym również przeglądarki.

template

Kolejnym elementem jest atrybut template, który pozwala nam zdefiniować szablon, który wykorzystywać będzie nasza dyrektywa. Dzięki temu możemy bindować dane przekazywane do dyrektywy lub generowane przez kontroler (patrz niżej) samej dyrektywy.

template: '<div>My extra template is here</div>'

templateUrl

Szablony umieszczać możemy również w osobnych plikach. Nierzadko będą one rozbudowane, więc tym łatwiej będzie nam je trzymać w osobnym pliku HTML, gdzie nasze IDE pozwoli nam też podpowiadać składnię, niżeli pisać szablon w postaci łańcucha znaków w JavaScript. Szablony zdefiniowane w ten sposób ładowane są asynchronicznie i cachowane.

templateUrl: 'templates/my-extra-template.html'

controller

Dyrektywy posiadać mogą własne, lokalne kontrolery, gdzie definiować możemy metody operujące na danych. Pozwala to budować logikę dyrektywy, którą następnie możemy używać chociażby w szablonie.

compile

Atrybut dyrektywy, który definiuje jej działanie w czasie kompilacji – jeszcze przed podpięciem samej dyrektywy do drzewa DOM. Faza compile zwraca funkcję link (patrz niżej), pozwalającą na bindowanie logiki dla elementu w drzewie DOM.

link

Finalne bindowanie logiki dyrektywy dla elementu w drzewie DOM. Jest wynikiem fazy kompilacji następującej przed samym linkowaniem.

require

Pozwala nam określić zależności w postaci innych dyrektyw, które wymagane są do działania naszej dyrektywy. Spójrzmy na definicję dyrektywy:

app.directive('myDirective', function () {
    return {
        restrict: 'A',
        require: '^ngModel'
    };
});

oraz przykład jej wywołania:

<div my-directive ng-model="city"></div>

Gdyby zabrakło tutaj wywołania ng-model, to otrzymalibyśmy błąd mówiący nam o tym fakcie.

Zauważyliście zapewne znak ^ przed nazwą ngModel. Informuje on kompilator AngularJS by szukać wywołania dyrektywy ngModel także poza elementem, dla którego zdefiniowano dyrektywę myDirective.

Istnieje również opcja ?, która spowoduje, że w przypadku braku zdefiniowanej zależności nie otrzymamy błędu kompilacji dyrektywy.

scope

Jak możecie się domyślać – jest to lokalny scope w naszej dyrektywie. Wywołując dyrektywę w HTML możemy do niej przekazywać różne parametry.

Spójrzmy na kawałek kodu:

scope: {
    param1: '@',  // parametr przekazywany przez wartosc (one-way binding)
    param2: '=',  // parametr przekazywany przez referencje (two-way binding)
    param3: '&'   // wyrazenie, ktore moze zostac wywolane w ciele dyrektywy
}

Pozwoli nam to zdefiniować dyrektywę w następujący sposób:

<div my-directive param1="{{ onlyValue }}" param2="toWayBindingByReference" param3="functionInvokedInDirective()"></div>

replace

replace: true

Pozwala na zamianę ciała elementu, dla którego definiujemy w całości przez szablon dyrektywy.

transclude

Możemy w ten sposób pobrać zawartość elementu, dla którego definiujemy dyrektywę, a następnie przekazać ją do szablonu dyrektywy.

transclude: true

Wyobraźmy sobie HTML w postaci:

<div my-directive>
    <p>To template please!</p>
</div>

Wykorzystanie opcji transclude w dyrektywie pozwoli nam na pobranie <p>To template please!</p> oraz przekazanie do szablonu w następujący sposób:

app.directive('myDirective', function () {
    return {
        restrict: 'A',
        template: '<div ng-transclude></div>',
        transclude: true
    };
});

Tym samym paragraf p przekazany zostanie do div szablonu dyrektywy.

Pierwsza dyrektywa

Poniżej przykład budowy oraz wykorzystania dyrektywy, która pozwalać będzie na wstawienie w dane miejsce dokumentu HTML reużywalnego komponentu w postaci listy z tytułem.

[wp-js-fiddle url="https://jsfiddle.net/mrzepinski/Ggur3/2/" style="width:100%;height:400px;border:solid #4173A0 1px;"]

Egghead

“First Directive”

[youtube_sc url=”https://www.youtube.com/watch?v=xoIHkM4KpHM”]

“Directive Restrictions”

[youtube_sc url=”https://www.youtube.com/watch?v=AoDh1T_0Obg”]

I na koniec

Mam nadzieję, że starczy mi czasu i determinacji na kolejne wpisy o AngularJS. Na chwilę obecną nie jestem w stanie przewidzieć o czym będzie mowa następnym razem. Przewiduję, że natchnie mnie w pewnym momencie na napisanie bardziej praktycznego wpisu. Sam temat jest z pewnością wart uwagi.