Seshadri S. - AngularJS. Szybkie wprowadzenie

O 'R E IL L Y 9 AngularJSSzybkie wprowadzenie BŁYSKAWICZNIE OPANUJ ANGULARJS! Helionie Shyam Seshadri, Brad Green Spis treści Wprowadzenie...

4 downloads 18 Views 19MB Size
< d i v n g-m e s s a g e = "e m a il" > P o d a j praw idło wy a d re s e - m a i l < / d i v > < s c r i pt t y p e = " t e x t / j a v a s c r i p t " > an g u la r . m o d u le ( ' n o t e s A p p [ ' ngM es sag es ' ] ) . c o n t r o lle r ( 'M a i n C t r l', [fu n ctio n () var s e lf = this; self.sub m it1 = function() {

{

c o n s o l e . l o g ( ' U ż y t k o w n i k z a t w i e r d z i ł fo r m u l a r z z danymi

',

}; se lf.su b m it2 = function() { c o n s o l e . l o g ( ' U ż y t k o w n i k z a t w i e r d z i ł fo r m u l a r z z d a n y m i',

se lf.u se r1);

se lf.u se r2);

}; }]);

W pierwszej chwili może się wydawać, że to długi i skomplikowany kod, ale bez trudu go zrozu­ miesz dzięki poniższemu opisowi fragm ent po fragm encie: • Utw orzyliśm y dwa form ularze zaw ierające po dwa pola wejściow e: na nazwę użytkownika i hasło. Pole nazwy użytkownika w obu form ularzach m a trzy weryfikatory: adresu e-m ail (jest typu em ail), oznaczający, że wypełnienie tego pola jest obowiązkowe, oraz określający, że m inim alna długość danych wynosi cztery znaki. Pole hasła m a tylko weryfikator, który oznacza, że jego wypełnienie jest obowiązkowe. • Zanim przyjrzymy się dyrektywie ng-messages, przejdziem y na koniec dokumentu. O prócz pliku an gu lar.js dołączam y także plik an gu lar-m essages.js zawierający kod źródłowy modułu ngMessages. Ponadto dodaliśmy ten m oduł jako zależność do modułu notesApp. • Zdefiniowaliśmy element < scrip t> typu text/ng-tem plate i z identyfikatorem error-m essages. Dzięki temu m ożem y definiować potrzebne nam fragm enty kodu H TM L wewnątrz naszego głównego pliku in d ex .h tm l. Zawartość tego znacznika m ożna by też było przenieść do osob­ nego pliku H TM L. • Kod H TM L zdefiniowany w elem encie < scrip t> zawiera domyślne pow iadomienia o błędach i warunki ich wyświetlania. W tym przypadku zdefiniow aliśm y w iadom ości o błędach dla weryfikatorów required i minlength. • Teraz wracamy do formularzy, a konkretnie do form ularza simpleForm. Po każdym polu tw o­ rzymy nowy elem ent
z dyrektywą ng-messages. Do dyrektywy tej przekazujemy obiekt $error, na podstawie którego chcemy wyświetlać wiadomości o błędach. Dyrektywa ng-messages szuka kluczy w tym obiekcie i jeśli dopasuje konkretny warunek, powoduje wyświetlenie k o­ munikatu w interfejsie użytkownika.

Obsługa błędów w formularzu

|

71

• Dyrektywa ng-messages umożliwia zdefiniowanie wiadomości o błędach bezpośrednio w do­ kum encie (jak w przypadku pola password w form ularzu overriddenForm) lub dołączenie ich z zewnętrznego szablonu za pom ocą atrybutu ng-m essages-include. • A trybu t n g -m essa g e s-in cl ude szuka osadzonego w d oku m encie szablonu, czyli elem entu < scrip t> typu text/ng-tem plate o określonym identyfikatorze bądź zewnętrznego pliku, który zostanie załadowany asynchronicznie przez system AngularJS. • K olejność definicji powiadom ień w szablonie (albo jako dzieci dyrektywy ng-messages) okre­ śla porządek ich wyświetlania. W jednym polu form ularza może jednocześnie występować kilka błędów. W naszym przypadku w polu nazwy użytkownika m oże zostać w pisany tekst zawierający m niej niż sześć znaków i niereprezentujący prawidłowego adresu e-m ail. Jako że minlength zdefiniowaliśmy przed em ail, inform acja o błędzie minlength zostanie wyświetlona pierwsza. A jeśli warunek dotyczący długości tekstu będzie spełniony, zostanie wyświetlona sama wiadom ość dotycząca adresu e-mail. • W razie potrzeby w iadom ości o błędach znajdujące się w ogólnych szablonach m ożna też przesłaniać. Dla pola username w form ularzu overriddenForm przesłoniliśm y w iadomość do­ tyczącą błędu required, zam ieniając ją na bardziej konkretną. N atom iast pow iadomienia dla minlength i email pozostawiliśmy bez zmian. Moduł ngMessages rozpoznaje, które komunikaty należy zm ienić, a które pozostawić. Dom yślnie m oduł ngMessages wyświetla tylko pierwszą w iadomość o błędzie z listy warunków ng-message. Jeśli chcemy wyświetlić wszystkie inform acje o błędach, które wystąpiły, należy dodat­ kowo użyć atrybutu ng-m essages-m ultiple. Dzięki niem u zostaną pokazane wiadomości o wszyst­ kich błędach, których warunki zostały spełnione. M odułu ngMessages m ożna też używać z własnymi wymaganiami i nie tylko w połączeniu z fo r­ m ularzami. M oduł ten sprawdza wartości kluczy danego obiektu i wyświetla kom unikaty, ja k in ­ strukcja switch. Podsumowując, moduł ngMessages pozwala znacznie uprościć mechanizm obsługi błędów w for­ mularzach w AngularJS.

Stylizowanie formularzy i stanów W cześniej opisaliśm y różne stany form ularzy (i ich pól wejściowych) — $ d irty , $v a lid itd. W iesz już, jak wyświetlać wybrane pow iadomienia o błędach i wyłączać przyciski na podstawie tych wa­ runków , ale nie um iesz jeszcze wyróżniać w ybranych pól form ularza lub całych form ularzy za p o m ocą arkuszy stylów. Jedną m ożliw ością je s t użycie stanów form u larzy i pól w ejściow ych w połączeniu z dyrektywą n g -cl ass w celu dodania np. klasy d ir ty , gdy spełniony je st w arunek myForm.$dirty. Jednak w systemie AngularJS istnieje prostsze rozwiązanie. Dla każdego z opisanych wcześniej stanów AngularJS dodaje i usuwa klasy CSS, opisane w tabeli 4.3, do formularzy i elementów wejściowych. A nalogicznie dla każdego weryfikatora dodanego do pól wejściowych również otrzym ujem y klasę CSS o podobnej nazwie, ja k widać w tabeli 4.4.

72

|

Rozdział 4. Formularze, pobieranie danych i usługi

T a b e la 4.3. K lasy CSS d la s ta n ó w fo r m u la r z y Stan

Klasa CSS

$invalid

ng-invalid

$ v a li d

ng-valid

$p ristine

ng-pristine

$dirty

ng-dirty

T a b e la 4.4. K lasy CSS d la s ta n ó w p ó l w ejściow ych Stan pola wejściowego

Klasa CSS

$invalid

n g-invalid

$ v a li d

n g - v a lid

$p ristine

ng-pristine

$dirty

ng-dirty

req uired

n g - v a lid - r e q u i r e d lub n g - i n v a l i d - r e q u i r e d

min

ng -v ali d -m in lub n g - in v a lid - m in

max

ng-valid -ma x lub n g- in valid -m ax

minlength

n g - v a lid - m i n le n g t h lu b n g -i n v a lid - m in le n g t h

maxlength

ng- valid -m axlengt hlub ng -inv ali d -m axle ng th

pa ttern

n g - v a lid - p a t t e r n lu b n g - i n v a l i d - p a t t e r n

url

ng-valid -urllub ng -invalid -url

email

n g - v a lid - e m a il lu b n g - in v a lid - e m a il

date

n g - v a lid - d a t e lu b n g - i n v a l i d-date

number

ng-valid-numberlub ng-invalid -n um be r

Oprócz stanu pola wejściowego AngularJS pobiera nazwę weryfikatora (number, maxlength, pattern itd.) i w zależności od tego, czy warunek tego weryfikatora został spełniony, czy nie, dodaje klasę n g -v aM d -n azw a_ w ery fik a to ra lub n g -in v a lid -n a z w a _ w e r y fik a to r a . Spójrzm y na przykład wykorzystania tego do wyróżniania pól wejściowych na różne sposoby: < t itle > N o te s A pp

102

I

Rozdział 6. Komunikacja z serwerem przy użyciu usługi $http

< h 1 > W i t a j c i e , s e rw e ry ! < d i v n g - r e p e a t = " t o d o in m a i n C t r l . i t e m s " c l a s s = " i t e m " > < s p a n n g - b i n d = " t o d o . l a b e l " > < / s p a n > < / d i v> < d i v > - by < / s p a n > < / d i v > < / d iv > < s c r i pt s r c = " h ttps://ajax.googleapi s.com / ajax/libs/an gu larjs/1.3.11/a n gu la r.js"> < s c r i pt> a n g u l a r . m o d u l e ( 'n o t e s A p p ' , []) . c o n t r o l l e r ( 'M a i n C t r l ', [ '$ h t t p ', function($http) { var s e lf = t h is ; se l f . i tems = [ ]; $ h ttp .g e t('/ a p i/ n o te ').th e n (f u n c tio n (re sp o n se ) { s e lf . ite m s = response.data; } , functio n(errRe spo nse ) { c o n s o le . e r r o r ( 'B ! ą d p o b ie ra n ia n o t a t e k ') ; }); }]);

Kod H T M L w tym kodzie je st bardzo prosty. Zawiera elem ent
powiązany z kontrolerem M ainCtrl. W elem encie tym znajduje się b lok n g -rep ea t, przeglądający elem enty tablicy items z kontrolera. W bloku tym wiążemy za pom ocą dyrektywy ng-bind pola lab el i author. K ontroler m a zależność w postaci usługi $h ttp . Gdy zostaje załadowany, wysyłamy żądanie GET do punktu końcowego serwera /a p i/n o tes. Funkcja $ h ttp .g e t( ) zwraca obiekt zwany ob ietn icą (zaraz rozwiniem y ten tem at), który umożliwia łączenie funkcji w łańcuchy, tak jakby były syn­ chroniczne. W yw ołanie serwera m oże zostać wykonane natychm iast lub po kilku sekundach. D zięki obiektow i ob ietn icy wiem y, kiedy serw er zw raca odpow iedź (zarów no pozytyw ną, ja k i negatywną), i m ożem y wykonać następną funkcję. Jest to ważne z tego względu, że obietnice (podobnie ja k wywołania zwrotne) pozwalają na roz­ wiązywanie problem ów dotyczących skalow alności. O bie te koncepcje decydują o nieblokującej i zdarzeniowej naturze języka JavaScript, dzięki której przeglądarka może wykonywać swoją pracę, podczas gdy serwer obsługuje żądanie. Niezależnie od tego, czy serwer m a do obsłużenia jedno żądanie, dziesięć żądań, czy nawet m ilion żądań, klient nie utknie w oczekiwaniu na odpowiedź. Może robić inne rzeczy, ponieważ interfejs użytkownika nie zostanie zawieszony. Fun kcja then przyjm uje dwa argumenty: p ro ced u rę o b słu g i pow od zenia i p ro ced u rę o b słu g i błędów. Jeżeli serwer zwróci inną odpowiedź niż 200, zostanie wywołana procedura obsługi błędów. W przeciwnym wypadku wywołana zostanie procedura obsługi powodzenia. Obu tym procedurom przekazywany jest obiekt odpowiedzi zawierający następujące klucze: headers Nagłówki dla wywołania. s ta tu s Kod statusu odpowiedzi.

Pobieranie danych za pomocą usługi $http i żądań GET

|

103

co n fig Konfiguracja, z którą wykonano wywołanie. data Treść odpowiedzi od serwera. W przypadku powodzenia po prostu przypisujemy dane z serwera do tablicy items i pozwalamy systemowi AngularJS zaktualizować interfejs użytkownika przez wiązanie danych. Natomiast błąd drukujemy w konsoli.

Obietnice W tym m om encie warto poświęcić chwilę na dogłębne zbadanie zagadnienia obietnic. W systemie AngularJS im plem entacja obietnic jest oparta na bibliotece Krisa Kowala Q (h ttp s://g ith u b .c o m / k r is k o w a l/q ), k tóra je st standardow ym i wygodnym narzędziem do pracy z asynchronicznym i wywołaniami w JavaScripcie. Tradycyjny sposób obsługi wywołań asynchronicznych w języku JavaScript polega na wykorzy­ staniu wywołań zwrotnych. Powiedzmy, że chcem y wysłać do serwera po kolei trzy wywołania, aby skonfigurow ać naszą aplikację. Przy użyciu wywołań zwrotnych kod m ógłby wyglądać m niej więcej tak (przy założeniu, że funkcja xhrGET wykonuje wywołania do serwera): // p obiera inform acje konfiguracyjne z serwera x h r G E T ('/ a p i/ s e rv e r-c o n fig ',

function (con fig)

{

/ / p obiera inform acje o użytkowniku, jeśli jest zalogowany x h r G E T ( ' / a p i / ' + con fig .U SER_ END_P OINT,

function(user)

{

// p obiera elementy d la użytkownika x h r G E T ('/ a p i/ ' + u s e r. id + '/ it e m s ',

function(item s)

{

/ / wyświetla pobrane elementy }); }); });

W przykładzie tym najpierw pobraliśm y dane konfiguracyjne z serwera. Następnie na tej podsta­ wie pobraliśm y in form acje o bieżącym użytkowniku i listę elem entów dla tego użytkownika. Każde wywołanie funkcji xhrGET pobiera funkcję zwrotną, która jest wykonywana, kiedy serwer zwróci odpowiedź. Im więcej poziom ów zagnieżdżenia, tym kod jest trudniejszy do odczytania, poprawiania, utrzy­ m ania, aktualizowania i ogólnie obsługi. Nazywa się to piekłem wywołań zwrotnych. Ponadto je ­ żeli zechcem y dodać obsługę błędów, to do każdego wywołania funkcji xhrGET m usim y przekazać kolejną funkcję, aby było wiadomo, co trzeba robić, gdy wystąpi błąd. Gdybyśmy chcieli utworzyć tylko jed ną wspólną procedurę obsługi błędów, byłoby to niewykonalne. In te rfe js A PI o b ietn ic m a za zadanie rozw iązać p roblem y z zagnieżdżaniem i obsługą błędów . Jego założenia są następujące: 1. Każde asynchroniczne zadanie zwraca obiekt promise. 2. Każdy obiekt promise będzie zawierał funkcję then, która może pobierać dwa argum enty re­ prezentujące procedury do wykonania w przypadku powodzenia i porażki.

104

|

Rozdział 6. Komunikacja z serwerem przy użyciu usługi $http

3. Procedura obsługi pow odzenia lub porażki w fu n kcji then zostanie wywołana tylko raz, po zakończeniu zadania asynchronicznego. 4. Funkcja then także zwraca obiekt promise, aby umożliwić łączenie wywołań w łańcuchy. 5. Każda procedura obsługowa (powodzenia lub porażki) może zwracać wartość, która zostanie przekazana do następnej funkcji w łańcuchu obietnic. 6. Jeśli procedura obsługowa zwraca obietnicę (wykonuje kolejne żądanie asynchroniczne), to następna procedura obsługowa (powodzenia lub porażki) zostanie wywołana dopiero po za­ kończeniu tego żądania. Przy użyciu obietnic i usługi $http poprzedni przykład kodu m ożna zam ienić na następujący: $ h t tp .g e t('/ a p i/ se rv e r-c o n fig ').th e n (fu n c tio n (c o n fig R e s p o n s e ) { r e t u r n $ h t t p . g e t ( ' / a p i / ' + co n fig R e s p o n s e. d ata . U SE R _EN D _P O IN T ) ; } ) .the n(function(userResponse) { return $ h t t p . g e t ( '/ a p i/ ' + use rR e sp onse.data.id + '/ i t e m s ') ; } ) .then(function(item Response) {

// wyświetlanie elementów }, f u n c t i o n ( e r r o r )

{

/ / wspólna procedura obsługi błędów });

W kodzie tym wykorzystaliśmy usługę $h ttp do wykonania serii wywołań do serwera. Każde wy­ wołanie $h ttp .g e t zwraca obiekt promise i za pom ocą funkcji then dodajem y procedurę obsługi powodzenia. W dwóch pierwszych procedurach wykorzystujemy odpowiedź serwera do w ykona­ nia kolejnego wywołania serwerowego. Każda procedura obsługi powodzenia w obietnicy zwraca kolejną obietnicę za pom ocą funkcji $h tt p .g e t. W ówczas system AngularJS czeka na odpowiedź od serwera na dane wywołanie i do­ piero po je j otrzymaniu przechodzi do następnej funkcji w łańcuchu obietnic. Ponadto wartość odpowiedzi serwera dla obietnicy zostanie przekazana jako argum ent do następnej procedury ob­ sługi pow odzenia w łańcuchu. A zatem pierwsza fu nkcja then otrzym a configResponse, druga fu n k cja then otrzym a w artość zw rotną proced u ry obsługi pow odzenia configR espon se, czyli userResponse, itd. Poza tym utworzyliśmy jed ną procedurę obsługi błędów, którą przekazujemy jako drugi argu­ m ent do ostatniej funkcji w łańcuchu obietnic. Jeśli w którejkolw iek z obietnic w łańcuchu zdarzy się błąd, AngularJS znajdzie najbliższą procedurę obsługi błędów i ją wykona. Dlatego niezależnie od tego, czy źródłem błędu będzie żądanie konfiguracji, czy danych użytkownika, wywołana zo­ stanie zawsze ta sama procedura obsługi błędów.

Propagacja Tworzenie łańcuchów obietnic to bardzo przydatna technika, otwierająca przed programistą wielkie możliwości, np. pozwala wysłać przez usługę wywołanie do serwera, przetworzyć odebrane dane i zwró­ cić je do kontrolera. Ale podczas pracy z obietnicam i należy pam iętać o kilku ważnych kwestiach. Spójrz na poniższy hipotetyczny łańcuch trzech obietnic P1, P2 i P3. Każda z nich m a procedury obsługi powodzenia i porażki: S1 i E1 dla P1, S2 i E2 dla P2 oraz S3 i E3 dla P3:

Pobieranie danych za pomocą usługi $http i żądań GET

|

105

xhrCal l ( ) . t h e n ( S 1 , E1) UPI .then(S2,

E2) //P2

.then(S3,

E3) //P3

W norm alnej sytuacji, gdyby nie było żadnych błędów, aplikacja wykonałaby po kolei procedury S1, S2 i S3. Ale w rzeczywistości nic nie idzie tak gładko. O bietnica P1 lub P2 może napotkać jakiś problem , przez co zostanie wywołana procedura E1 albo E2. I teraz na podstawie wartości zwrotnej tych procedur system AngularJS wybierze następną funk­ cję z łańcucha do wywołania. W każdej z tych procedur program ista m a kontrolę, może więc zde­ cydować, w odniesieniu do bieżącej procedury obsługow ej, którą fu nkcję w łańcuchu wykonać w następnej kolejności. Rozważ poniższe przypadki: • Otrzym aliśm y pozytywną odpowiedź od serwera dla obietnicy P1, ale zwrócone dane nie są poprawne lub na serwerze w ogóle brak jest dostępnych danych (pusta tablica). W takim przy­ padku dla następnej obietnicy P2 powinna zostać wywołana procedura obsługi błędów E2. • Dla obietnicy P2 otrzym ujem y błąd powodujący wywołanie procedury E2. Jednak w proce­ durze tej m am y dane z bufora pozwalające n orm alnie załadować aplikację. W takim przy­ padku m ożem y sprawić, aby po procedurze E2 została wywołana procedura S3. A zatem za każdym razem, gdy piszemy procedurę obsługi powodzenia albo błędów, musimy wykonać wywołanie — dla naszej bieżącej funkcji, czy ta obietnica jest powodzeniem, czy porażką dla następnej procedury w łańcuchu obietnic? Jeśli dla następnej obietnicy w łańcuchu chcem y wywołać procedurę obsługi powodzeń, m ożem y po prostu zwrócić wartość w procedurze obsługi powodzeń lub błędów, a system AngularJS po­ traktuje to jako pomyślne rozwiązanie ewentualnych problemów. Jeżeli jednak dla następnej obietnicy w łańcuchu chcem y wywołać procedurę obsługi błędów, m o­ żemy wykorzystać usługę $q systemu AngularJS. Wystarczy ją zdefiniować jako zależność w swoim kontrolerze i usłudze oraz zwrócić $ q .r e je c t(d a ta ) z procedury obsługowej. To sprawi, że następna obietnica w łańcuchu przejdzie w stan błędu i otrzyma dane przekazane do niej jako argument.

Usługa $q Usługa $q w systemie AngularJS m a następujący interfejs API: $ q .d e fe r() Tworzy odroczony obiekt, gdy musimy utworzyć obietnicę dla naszego własnego zadania asyn­ chronicznego. W iększość zadań asynchronicznych w AngularJS (wywołania serwerowe, limity czasu i interwały) zwraca obietnicę, ale jeśli używana jest biblioteka zewnętrzna, to potrzebna może być własna obietnica. W takich przypadkach przydatna jest funkcja $ q .d e fe r(), ponieważ odroczony obiekt zawiera atrybut promise, który może zostać zw rócony przez funkcję. d e fe rre d O b je ct.re so lv e O biekt odroczony utworzony przez poprzednią funkcję m ożna rozwiązać w dowolnym m o­ m encie, wywołując na nim funkcję re s o lv e () i przekazując je j jako argum ent dane przesłane do procedury obsługi powodzenia w łańcuchu obietnic.

106

|

Rozdział 6. Komunikacja z serwerem przy użyciu usługi $http

d e fe r r e d O b je c t.r e je c t O biekt odroczony może też zostać odrzucony, co oznacza, że obietnica była porażką i że zo­ stała wywołana je j procedura obsługi niepowodzeń. Przekazany do niej argum ent zostanie oddany do procedury obsługi błędów w takim stanie, w jakim jest. $ q .r e je c t Funkcję $ q .r e je c t m ożna wywołać z dowolnej procedury obsługowej obietnicy (powodzenia lub błędu) z opcjonalnym argum entem określającym wartość do przekazania wzdłuż łańcu­ cha obietnic. Powinna zostać zw rócona wartość zwrotna, aby zapewnić przejście do następnej procedury obsługi błędów, a nie procedury obsługi powodzeń.

Wykonywanie żądań POST przy użyciu usługi $http N a podstawie zdobytych wiadomości m ożemy zbudować pozostałą część interfejsu użytkownika do naszego przykładu. Dodam y sekcję um ożliwiającą dodawanie notatek do listy: < t i t l e > H T T P P o st E x a m p l e < / t it le >

< h 1 > W i t a j c i e , s e rw e ry ! < d i v n g - r e p e a t = " t o d o in m a i n C t r l . i t e m s " class="item "> < d iv > < s p a n n g - b i n d = " t o d o . l a b e l " > < / s p a n > < / d i v> < d i v > - by < / s p a n > < / d i v > < / d iv >
< input ty p e = "te x t" p la c e h o ld e r= "E ty k i eta" ng-model = "mai n C t r l . n e w T o d o . l a b e l " r e q u ir e d > < input ty p e = "te x t" place hold e r="Au tor" ng-model = "mai n C t r l. n e w T o d o . a u t h o r " r e q u ir e d > < input type="subm it" val ue = "D o d a j" ng-di sa bled="a dd Form .$inv alid ">
< / d iv > < script

Pobieranie danych za pomocą usługi $http i żądań GET

|

107

s r c = " https://ajax.googleapi s.c o m / ajax / lib s/ an gu larjs/ 1 .3 .1 1 / a n gu la r.js"> a n g u l a r . m o d u l e ( 'n o t e s A p p ' , []) . c o n t r o l l e r ( 'M a i n C t r l ', [ '$ h t t p ', function($http) { var s e l f = th is; sel f . i tems = [] ; se lf . n e w T o d o = { } ; v a r fe tc hT o d os = f u n c t i o n ( )

{

return $ h t tp .g e t('/ a p i/ n o te ').t h e n ( function(response) { s e lf . ite m s = response.data; }, f u n c t i o n ( e r r R e s p o n s e ) { c o n s o le .e r ro r ('B łą d pobierania n o ta te k .') ; }); }; fetchTodos(); se lf.a d d = function() { $ h t t p . p o s t ( ' / a p i / n o t e ' , se lf.ne wT o do ) .t h e n ( fe t c h T o d o s ) .then(function(response) { s e lf .ne w T o do = { } ; }); }; }]);

W przykładzie tym dodaliśmy nową sekcję do kodu H TM L składającego się ze standardowego form ularza i dwóch pól wejściowych. Pola te powiązaliśmy z naszym modelem , newTodo, w kon­ trolerze. Po zatwierdzeniu form ularza następuje uruchom ienie funkcji a d d () w kontrolerze. W tym przypadku k on troler je s t nieco inny niż poprzednio. M ech anizm p o bieran ia n otatek z serwera znajduje się teraz w funkcji fe tch T o d o s(), która nie tylko wysyła do serwera wywołania $h t t p .g e t , ale również zwraca obietnicę dla wywołania asynchronicznego. Fun kcja ta jest wywo­ ływana raz, przy ładowaniu kontrolera. Fu n kcja add także wykorzystuje usługę $ h ttp i wywołuje funkcję $h ttp .p o s t. W odróżnieniu od żądania GET, które pobiera jed en argum ent w postaci adresu U R L serwera, żądanie POST pobiera dwa argum enty: adres U R L i przesyłane dane. W yw ołanie serwerowe w przypadku pom yślnego utworzenia notatki łączymy z wywołaniem funkcji fetchTodos. Później dodajem y kolejną obiet­ nicę do łań cu ch a, któ ra wyczyści obiekt newTodo. P roced u ra obsługow a tej ostatn iej ob ietn icy zostanie wywołana dopiero po w yw ołaniach serwerow ych m ających na celu utworzenie notatki i pobranie listy notatek (ze względu na obietnicę zwracaną przez funkcję fetchTodos).

Interfejs API usługi $http Już jakiś czas wykorzystujemy usługę $ h ttp do pobierania i zapisywania danych, więc dobrze by było przyjrzeć się je j interfejsow i API.

108

|

Rozdział 6. Komunikacja z serwerem przy użyciu usługi $http

Usługa $http zapewnia następujące metody pom ocnicze do wykonywania różnych rodzajów żądań: • GET • HEAD • POST • DELETE • PUT • JSONP W związku z tym do dyspozycji m am y nie tylko fu nkcję $ h t t p .g e t , lecz także np. $ h ttp .p u t i $h tt p .d e le te . Sygnatura każdej z tych funkcji jest zbudowana według jednego z dwóch wzorów: • Funkcje żądań niew ysyłających danych w treści (np. GET) pobierają dwa argum enty: adres U R L na pierwszym m iejscu i obiekt konfigu racji na drugim. • Funkcje żądań wysyłających dane w treści (np. POST i PUT) pobierają trzy argumentu: adres URL na pierwszym miejscu, przesyłane dane na drugim m iejscu oraz obiekt konfiguracji na trzecim. Każda z nich jest m etodą pom ocniczą, którą m ożna wywołać bezpośrednio przez obiekt $http, np. ten kod: $http .get(url,

config)

m ożna zastąpić tym: $http (c onfig)

W drugim przypadku param etr url i m etoda żądania (tu GET) są określone przez obiekt konfigu­ racji. W każdej metodzie pom ocniczej ($ h tt p .g e t, $ h ttp .p o s t itd.) obiekt konfiguracyjny, okre­ ślany w ostatnim param etrze, je st opcjonalny. W związku z tym m etodę $ h tt p .g e t m ożna wy­ woływać tylko z podaniem adresu U R L , jak to robiliśm y w tych przykładach.

Konfiguracja Sporo piszem y o obiekcie konfiguracyjnym , czas przyjrzeć się jego param etrom i w artościom . Poniżej znajduje się pseudokod przedstawiający szablon budowy obiektu konfiguracyjnego z wy­ kazem kluczy i typów wartości, jakich m ożna używać: { method: ł ańcuc h, u r l : ł ańcuc h, params: o b i e k t , data: ła ń cuc h lu b o b i e k t , head ers : o b i e k t , xsrfHea derName: ł ań cuc h , xsrf C o o ki e N am e : ł ań cuc h , t r a n s fo rm R e q u e s t: f u n c t i o n t ra n s fo rm ( d a n e , po bieraczN agłów ków) lub ta b lic a funkcji, t r a n s fo rm R e sp o n se : f u n c t i o n t ra n s f o r m ( d a n e , po bieraczN agłów ków) lu b ta b lic a funkcji, cache: w a r to ść l o g i c z n a lu b o b i e k t b u f o r a , timeo ut: l i c z b a , w i t h C r e d e n t i a l s : w a r to ść l o g i c z n a }

Pobieranie danych za pomocą usługi $http i żądań GET

|

109

M etody pom ocnicze same ustawiają param etr m etody żądania, więc program ista nie musi ju ż te­ go robić. Analogicznie jeśli metodzie żądania GET lub POST przekaże się adres URL, to zostanie on automatycznie ustawiony w konfiguracji. Żądanie i sposób jego działania m ożna zm ienić przez przekazanie obiektu co n fig z następującymi kluczami: method Łańcuch reprezentujący typ żądania H TT P , np. GET bądź POST. url Łańcuch reprezentujący adres U R L będący względną albo bezwzględną ścieżką do żądanego zasobu. params O biekt JavaScript z kluczami i wartościam i odpowiadającymi kluczom i w artościom para­ m etru zapytania URL. N a przykład: [{k luc z1 :

'w a r t o ść 1 ',

k lu c z 2 :

'w a rt o ś ć 2 '}]

zm ieni się w: ?klucz1=wartość1&klucz2=wartość2

Gdyby w wartości zamiast łańcucha lub liczby podano obiekt, zostałby on przekonwertowany na łańcuch w form acie JSON. data Łańcuch bądź obiekt, który zostanie wysłany jako dane żądania. D la serwera są to po prostu inform acje POST. headers O biekt (lub słow nik), którego każdy klucz jest nazwą nagłówka, a wartość każdego klucza jest w artością odpowiedniego nagłówka. Na przykład {'C o n te n t-T y p e ':

't e x t / c s v '} oznacza

ustawienie nagłówka Content-Type na text/ csv . xsrfHeaderName Um ożliw ia zdefiniowanie nagłówka X SR F, który serwer będzie ustawiał w celu uniem ożli­ w ienia ataków X S R F na stronę. N agłów ek ten zostanie użyty do w eryfikacji, czy m iędzy klientem i serwerem panuje zgodność parametrów. xsrfCookieName Nazwa ciasteczka zawierającego token x s r f do użycia w uzgadnianiu X SR F. transform Request i transformResponse Um ożliw iają zm ienienie danych dla wychodzącego żądania lub przychodzącej odpowiedzi. Należy przekazać pojedynczą funkcję (której podaje się dane i m etodę pobrania nagłówków) albo tablicę funkcji. Każda z nich może pobierać dane (będące przesyłaną treścią bądź odpo­ wiedzią) i zwracać przekształcone dane. Poniżej znajduje się przykładowa d efinicja fu nkcji pobierającej dane w form acie JSO N i konw ertującej je na łańcuch zapytania: t ra n s fo rm R e q u e s t : f u n c t i o n ( d a t a , var requestStr; f o r ( v a r key in data) { if

110

|

(requestStr)

he aders)

{

{

Rozdział 6. Komunikacja z serwerem przy użyciu usługi $http

r e q u e s t S t r += ' & ' + key + ' = ' + d a t a [ k e y ] ; } e lse { r e q u e s t S t r = key + ' = ' + d a t a [ k e y ] ; } } return requ e stStr; }

cache W artość logiczna lub obiekt bufora na użytek m echanizm u buforow ania aplikacji. Bufor ten może znajdować się nad buforem przeglądarki internetow ej. Ustawienie tego klucza na tru e spowoduje automatyczne buforow anie przez AngularJS odpowiedzi serwera i zwracanie ich dla następnych żądań do tego samego adresu URL. timeout Liczba milisekund, po której żądanie zostaje uznane za przeterminowane. Może to być też obiekt obietnicy, który w razie odrzucenia nakazuje AngularJS porzucenie wywołania serwerowego.

Zaawansowane właściwości usługi $http D o tej pory pokazywaliśmy tylko, ja k tworzyć proste żądania GET i POST za pom ocą usługi $h ttp , oraz rzuciliśm y okiem na m ożliw ości konfiguracji na poziom ie żądania. I właśnie ja k na razie używaliśmy usługi $ h ttp jedynie do wysyłania żądań. Ale za je j pom ocą m ożna też konfigurować ustawienia domyślne oraz przechwytywać wszystkie wychodzące żądania i przychodzące odpo­ wiedzi, aby je obsłużyć w jednolity sposób. Te zagadnienia są tem atem niniejszego podrozdziału.

Konfigurowanie ustawień domyślnych usługi $http Na pierwszy ogień weźmiemy konfigurow anie ustawień domyślnych usługi $h ttp . Z poprzednie­ go podrozdziału wiemy już, ja k dodawać przekształcenia i nagłówki w konfiguracji pojedynczego żądania $h ttp . Gdybyśmy chcieli dodać nagłówek buforow ania w każdym żądaniu, to szybko by­ śmy się znużyli, robiąc to w każdym wywołaniu $h ttp . W takich przypadkach m ożna wykorzystać sekcję co n fig modułu i dostawcę $httpProvid er. Poniżej pokazujemy przykład konfiguracji kilku nagłówków i domyślnego ustawienia transform Request za pom ocą dostawcy $httpProvider: / / Plik: r06/public/http-defaults.js a n g u l a r . m o d u l e ( 'n o t e s A p p ' , [ ]) . c o n t r o lle r ( 'L o g in C t r l', [ '$ h t t p ', var s e l f = th is; s e l f . u s e r = {}; se lf.m essage = 'Z a lo g u j s i ę ' ;

function($http)

{

s e l f . l o g i n = function() { $ h t t p . p o s t ( '/ a p i/ l o g in ', s e lf . u se r ). t h e n ( function(resp) { self.m essage = resp.data.msg; }); }; }]) . c o n f ig ( [ '$ h t t p P r o v id e r ',

fu nction($http P rovid er)

{

// dane POST są przekształcane na styl jQuery $http Provid e r.de fa ults.transfo rm R e que st.push(

Zaawansowane właściwości usługi $http

|

111

fu n ctio n (d ata) { var requestStr; i f (data) { d a ta = J S O N . p a r s e ( d a t a ) ; f o r ( v a r key in data) { i f (requestStr) { r e q u e s t S t r += ' & ' + key + ' = ' } else { r e q u e s t S t r = key + ' = '

+ data[key];

+ data[key];

} } } return requestStr; });

/ / ustawia typ treści wszystkich żądań POST na form ularzow y / / nagłówek ten nie zostanie dodany do żądań GET $ h t t p P r o v i d e r . d e f a u l t s . h e a d e r s . p o s t [ ' C o n t e n t - T y p e '] ' a p p l i c a t i o n/x-w w w -fo rm -url e nco de d';

=

}]);

W przykładzie tym zdefiniow aliśm y pewne ustaw ienia aplikacji dla usługi $h ttp . W tym celu utworzyliśmy sekcję con fig dla naszego modułu i wstrzyknęliśmy do niej dostawcę $httpProvider. W ram ach konfiguracji najpierw dodaliśmy globalny transform ator żądań zm ieniający dane POST żądań z form atu JSO N na form at łańcuchow y jQ u ery. Zw róć uwagę, że wstawiliśmy tę funkcję przekształcającą do domyślnych transformatorów. Takie ustawienia ja k w tym przykładzie m ożna by było wprowadzić przy pracy z zapleczem ak­ ceptu jącym typ treści text/www-form-urlencoded, czyli dom yślny typ dla jQ u ery . W system ie AngularJS domyślny typ treści to a p p lica tio n / jso n , zalecany dla aplikacji sieciowych. Jeśli nie m ożesz przestawić swojego zaplecza na ten typ, to m ożesz dodać tran sform ator i nagłówek, by zmusić swoją aplikację AngularJS do współpracy z tym zapleczem. Istnieje m ożliwość utworzenia kilku transform atorów dla żądań i odpowiedzi, zarówno na po­ ziom ie pojedynczych żądań, ja k i globalnym , dlatego transform Request i transform Response są domyślnie tablicam i. W ystarczy wstawić do nich potrzebne funkcje. Przydałoby się też dodać domyślny nagłówek dla wszystkich wychodzących żądań GET. Obiekt $h ttp P ro v id er.d efau lts.h ead ers umożliwia ustawienie domyślnych nagłówków dla żądań common, get, post i put. Każde z nich (np. $ h ttp P ro v id e r.d e fa u lts .h e a d e rs .p o s t) jest obiektem , którego klucz to nazwa nagłówka, a w artość to w artość tego nagłówka. W przedstaw ionym przykładzie ustawiliśmy nagłówek Content-Type dla wszystkich wychodzących żądań POST. Poniżej znajduje się lista kluczy i wartości, którym m ożna zdefiniować ustawienia domyślne za pom ocą dostawcy $httpProvid er (przy użyciu własności $ h ttp P ro v id e r.d e fa u lts): • headers.common • h ead ers.g et • headers.put • head ers.post • transform Request

112

|

Rozdział 6. Komunikacja z serwerem przy użyciu usługi $http

• transformResponse • xsrfHeaderName • xsrfCookieName transform Request i transformResponse to tablice funkcji. Klucze dotyczące X SR F pobierają czyste łańcuchy. Nagłówki są słownikam i z kluczami określającym i nazwy nagłówków i wartościam i re­ prezentującym i wartości tych nagłówków.

Interceptory O bsługa czynności na poziom ie żądania (logowanie, uwierzytelnianie i obsługa różnych rodzajów odpowiedzi) w sposób globalny zawsze jest trudna. W ym aga dokładnego zaplanowania warstwy, przez którą będą przesyłane wszystkie żądania, aby można było dodać globalne punkty zaczepienia. Ale system A ngularJS znacznie to upraszcza przez użycie dostawcy $ h ttp P ro v id er do tw orzenia intercep torów . Interceptory w systemie AngularJS um ożliwiają podczepianie się do żądań i od­ powiedzi oraz obsługiwanie określonych zdarzeń (np. błędów 403, zwracanych przez serwer z po­ wodu problem ów z uwierzytelnianiem) w jednolity sposób. Stary styl tworzenia interceptorów odpowiedzi jest wycofywany i w przyszłych wer­ sjach systemu AngularJS posługiwanie się nim może stać się niemożliwe. Dlatego nie używaj więcej własności $httpProvider.responseInterceptors. Gdy program ista utworzy interceptor (a m ożna utworzyć ich wiele), system AngularJS wywołuje go przed wysłaniem jakiegokolw iek żądania do serwera. Ponadto system dopilnowuje, by in ter­ ceptor został wywołany przed każdym kontrolerem i każdą usługą wykonującą wywołania $http. Poniżej pokazujem y przykładową im plem entację interceptora: / / Plik: r06/public/logging-interceptor.js a n g u l a r . m o d u l e ( 'n o t e s A p p ' , [ ]) . c o n t r o lle r ( 'M a i n C t r l', [ '$ h t t p ', var s e l f = th is; s e l f . i t e m s = [ ];

function($http)

{

s e lf .n e w T o do = { } ; v a r fe tc hT o d os = f u n c t i o n ( ) { return $ h ttp .g e t('/ a p i/ n o te ').th e n (f u n c tio n (re sp o n se ) s e lf . ite m s = response.data; }, f u n c t i o n ( e r r R e s p o n s e ) { c o n s o le .e r r o r ('B łą d pob ierania n o t a t e k .') ;

{

}); }; fetchTodos(); se lf.a d d = function() { $ h t t p . p o s t ( ' / a p i / n o t e ' , se lf.ne wT o do ) .t h e n ( fe t c h T o d o s ) .then(function(response) { s e lf .n e w T o do = { } ; }); };

Zaawansowane właściwości usługi $http

|

113

} ] ) . f a c t o r y ( 'M y L o g g in g In t e r c e p t o r ', [ '$ q ' , function($q) { return { request: f u n c t io n ( c o n f ig ) { c o n s o l e . l o g ( ' Ż ą d a n i e wykonane p r z y u ż y c i u ' , c o n f i g ) ; return co nfig;

/ / w przypadku błędu lub braku pozw olenia // return $q.reject('Operacja niedozwolona'); }, requestError: fu n ctio n (re je ction ) { c o n s o l e . l o g ( ' B ł ą d ż ą d a n ia spowodowany ' ,

rejection);

// kontynuacja, aby następny łańcuch obietnic wykrył błąd return $ q .r e je c t( r e j e c t io n );

/ / lub w przypadku popraw nej obsługi // return someValue; }, re s p o n s e : f u n c t i o n ( r e s p o n s e ) { c o n s o l e . l o g ( 'O d p o w i e d ź od s e r w e r a ', r e s p o n s e ) ;

// zwraca obietnicę r e t u r n re s p o n s e || $ q .w h e n ( re s p o n s e ); }, responseError: fu n ctio n (re je ctio n ) { c o n s o l e . l o g ( ' B ł ą d w odp owiedzi ' , r e j e c t i o n ) ;

// kontynuacja, aby następny łańcuch obietnic wykrył błąd // w razie potrzeby m ożna tu sprawdzić kod statusu uwierzytelniania: // i f (rejection.status === 403) { / / p o k a ż okn o dialogowe logowania / / zwróć wartość inform ującą kontrolery, że błąd został obsłużony //1 / / albo m ożna zwrócić odm ow ę w celu kontynuowania łańcucha niepowodzeń obietnic return $ q .r e je c t( r e j e c t io n ); } }; }]) . c o n f ig ( [ '$ h t t p P r o v id e r ', fu nction($http P rovid er) { $ h t t p P r o v i d e r . i n t e r c e p t o r s . p u s h ( 'M yL o ggi n g I n t e r c e p t o r ' ) ; }]);

W przykładzie tym zaim plementowaliśmy interceptor rejestrujący wszystkie wychodzące żądania i przychodzące od serwera odpowiedzi. W systemie AngularJS interceptory im plem entuje się jako fabryki zwracające obiekt zawierający jed ną lub wszystkie z poniższych czterech metod: request Każde żądanie wychodzące przechodzi przez funkcję requ est, której dodatkowo przekazuje się konfigurację dotyczącą tego żądania. W tym m iejscu m ożna przyjrzeć się adresowi URL, wysyłanym danym i m etodzie żądania (GET, POST itd.) oraz zdecydować, czy kontynuow ać wysyłanie żądania (w tym przypadku zwracamy obiekt co n fig ), czy je odrzucić (przy użyciu instrukcji retu rn $ q .r e je c t , która odrzuca obietnicę). requ estE rror M etoda ta jest wywoływana, gdy istnieje kilka interceptorów i jed en z nich odrzuci bieżące żądanie. W takim przypadku do funkcji tej przekazywany jest powód odrzucenia (argum ent do $ q .r e je c t ) .

114

|

Rozdział 6. Komunikacja z serwerem przy użyciu usługi $http

resp o n se

Funkcja ta jest wywoływana z obiektem odpowiedzi (zawierającym konfigurację, kod statusu, nagłówki i dane) po zwróceniu odpowiedzi przez serwer. Jeśli trzeba sprawdzić poprawność danych lub wybranego nagłówka albo zarejestrować odpowiedź, to ta funkcja jest odpowied­ nim do tego m iejscem . re sp o n se E rro r

Jeżeli serwer zwróci kod statusu spoza rodziny 2xx, AngularJS traktuje to jako błąd. Funkcja ta także służy do obsługi odpowiedzi. M ożna w niej sprawdzić status, w ykonać dodatkowe czynności (np. wyświetlić okno dialogowe logowania w przypadku statusu 403), a następnie zw rócić odm ow ę (aby nakazać przyszłym ob ietn ico m traktow anie tego ja k o porażki) lub wartość oznaczającą, że wszystkie błędy zostały pomyślnie obsłużone. Fabryka ta zasadniczo decyduje o sposobie działania naszego interceptora i o tym, ja k obsługuje każdy z czterech wym ienionych przypadków. Jeśli program ista stwierdzi, że w danym interceptorze potrzebuje tylko m etody r e s p o n s e E r r o r , to może zaim plem entować fabrykę zwracającą obiekt zawierający wyłącznie tę funkcję. Na koniec interceptor podłączamy do usługi $ h t t p przy użyciu dostawcy $ h t t p P r o v i d e r w funkcji c o n fig .

Dostawca $ h t t p P r o v i d e r m a tablicę interceptorów, do której można dodawać kolejne przez

podanie nazwy. Zatem po prostu wstawiliśmy nasz interceptor M y L o g g i n g I n t e r c e p t o r do tej tablicy, dzięki czemu będzie on automatycznie włączany po zakończeniu ładowania aplikacji AngularJS.

Najlepsze praktyki Zgłębiliśm y tajniki usługi $ h t t p , pokazaliśmy, ja k konfigurować żądania, przechwytywać odpo­ wiedzi i wiele więcej. Poniżej przedstawiam y jeszcze listę rzeczy, które trzeba m ieć na uwadze podczas pracy z usługą $ h t t p : O pakow uj $http w usługi W zaprezentow anych przykładach wywoływaliśmy m etody $ h t t p . g e t i p o s t bezpośrednio w k o n tro lera ch . W praw dziw ej a p lik a cji pow in no się to ro b ić tak: zam iast wyw oływać $ h t t p .g e t ( / a p i/ n o t e s )

bezpośrednio z kontrolera, należy opakować to wywołanie w usługę,

aby m óc tworzyć wywołania typu N o t e S e r v i c e . q u e r y ( ) , które odpowiadałyby za wykonywa­ nie wywołań $ h t t p . g e t . Usługa ta może potem zwracać obietnicę, by kontroler m ógł odpo­ wiednio łańcuchow o obsłużyć odpowiedź: a n g u la r . m o d u le ( 'n o t e s A p p ', []) . f a c t o r y ( 'N o t e S e r v i c e ', [ '$ h t t p ', f u n c t io n ( $ h t t p ) retu rn { q u e ry: f u n c t io n ( ) { r e t u r n $ h t t p . g e t ( '/ a p i / n o t e s ') ;

{

} }; }]);

Przykład ten ilustruje, ja k m ogłoby wyglądać takie opakowanie N o t e S e r v i c e . Jego jedynym zadaniem je st zapakowanie wywołania $ h t t p w m etodę usługową i zw rócenie obietnicy, do której kontrolery i inne usługi będą m ogły dołączyć swoją funkcjonalność.

Zaawansowane właściwości usługi $http

|

115

Używaj in tercep toró w Czasam i za każdym razem, gdy klient wysyła żądania, trzeba wykonać pewne czynności, takie jak zarejestrowanie żądania lub dodanie nagłówków autoryzacyjnych do żądania bądź spraw­ dzenie pewnych warunków. W takim przypadku najlepszym rozwiązaniem są interceptory. Prosty interceptor obsługujący błędy 403 i dodający nagłówek autoryzacji w każdym żądaniu może wyglądać m niej więcej tak: a n g u la r . m o d u le ( 'n o t e s A p p ', []) . f a c t o r y ( 'A u t h In t e r c e p t o r ', [ 'A u t h I n f o S e r v i c e ', '$ q ', f u n c t io n ( A u t h In f o S e r v ic e , $q) retu rn { re q u e st: f u n c t io n ( c o n f ig ) { i f ( A u t h In fo S e r v ic e . h a s A u t h H e a d e r( ) ) { c o n f ig . h e a d e r s [ 'A u t h o r i z a t i o n '] = A u t h In f o S e r v ic e . g e t A u t h H e a d e r ( ) ;

{

} re tu rn c o n fig ; }, r e s p o n s e E r r o r : fu n c t io n ( r e s p o n s e R e j e c t io n ) i f ( r e s p o n s e E r r o r . s t a t u s === 403) {

{

/ / problem z autoryzacją, odm ow a dostępu A u t h In f o S e r v i c e . r e d ir e c t T o L o g in ( ) ; } r e t u r n $ q . r e j e c t ( r e s p o n s e R e j e c t io n ) ; } }; }]) . c o n f i g ( [ '$ h t t p P r o v i d e r ', f u n c t io n ( $ h t t p P r o v id e r ) { $ h t t p P r o v id e r . i n t e r c e p t o r s . p u s h ( 'A u t h In t e r c e p t o r ') ; }]);

W przykładzie tym zdefiniowaliśmy interceptor przechwytujący tylko wychodzące żądania i przy­ chodzące odpowiedzi z kodem statusu innym niż 200. Jeśli chodzi o żądania wychodzące, doda­ jem y nagłówek autoryzacji, jeżeli jest obecny w usłudze o nazwie A u t h I n f o S e r v i c e . W przypadku odpowiedzi sprawdzamy, czy kod statusu to 403 — jeśli tak, przekierowujemy użytkownika na stronę logowania. Sprawiamy, że obietnica zostaje odrzucona, dzięki czemu kontroler lub usługa dostrzeże pojawienie się błędu. Implementacja usługi A u t h In f o S e r v i ce zależy od potrzeb programu. Łącz in tercep to ry w łańcuchy Zamiast tworzyć jeden gigantyczny interceptor wykonujący wszystkie zadania związane z prze­ chwytywaniem, lepiej jest utworzyć kilka m niejszych interceptorów o ściśle określonej funkcji. M am y osobny interceptor dla autoryzacji, osobny dla rejestrow ania danych w dzienniku itd. Interceptory są wywoływane w kolejności dodania do dostawcy, więc programista m a nad nimi kontrolę w tym zakresie. D efin iu j ustaw ienia dom yślne Jeśli ciągle musisz ustawiać te same nagłówki albo definiować te same przekształcenia żądań lub odpowiedzi, to pow inieneś poważnie rozważyć m ożliw ość zdefiniow anie ustawień d o­ myślnych. Jeżeli wszystkie zakończenia serwera zwracają dane X M L zamiast JSO N , dodaj do dostawcy $ h t t p P r o v i d e r domyślną funkcję t r a n s f o r m R e s p o n s e , pobierającą dane w form acie X M L i przekształcającą je na form at JSO N (albo odwrotnie, jeśli serwer przyjmuje tylko fo r­ m at XM L). Ponadto w razie potrzeby m ożna określić ustawienia domyślne wyłącznie dla żądań GET

116

|

lub PO ST , co pozwala na precyzyjne działanie.

Rozdział 6. Komunikacja z serwerem przy użyciu usługi $http

Moduł ngResource W iesz ju ż prawie wszystko o usłudze $http, więc teraz przyjrzymy się opcjonalnem u modułowi ngResource i sprawdzimy, ja k m ożna go w ykorzystać w niektórych z opisanych przykładowych programów. Jeśli serwer udostępnia A PI R EST-ow e, m ożem y jeszcze bardziej zm niejszyć ilość kodu do napi­ sania, stosując opcjonalny m oduł AngularJS o nazwie ngResource. M oduł ten umożliwia utwo­ rzenie usługi A n g u larJS z końców ki A PI. W eźm y np. in terfejs A P I dla p ro jek tów na serwerze o następujących właściwościach: • Żądanie GET do / api/ project/ zwraca tablicę projektów. • Żądanie GET do /api/project/17 zwraca projekt o identyfikatorze 17. • Żądanie POST do / api/ project/ z obiektem projektu w form acie JSO N tworzy nowy projekt. • Żądanie POST do /api/project/19 z obiektem projektu w form acie JSO N aktualizuje projekt o identyfikatorze 19. • Żądanie DELETE do /api/ project/ usuwa wszystkie projekty. • Żądanie DELETE do /api/project/23 usuwa projekt o identyfikatorze 23. Jeśli m am y taki interfejs A PI, to zamiast ręcznie tworzyć usługę projektów i pojedynczo opakowywać żądania $http, m ożem y utworzyć następującą usługę: a n g u la r . m o d u le ( 'r e s o u r c e A p p ', [ ' n g R e s o u r c e '] ) . f a c t o r y ( 'P r o j e c t S e r v i c e ', [ '$ r e s o u r c e ', f u n c t io n ( $ r e s o u r c e ) r e t u r n $ r e s o u r c e ( '/ a p i / p r o j e c t / : i d ') ;

{

}]);

W ten sposób automatycznie otrzymalibyśm y m etody obiektu P ro je c tS e rv ic e , takie ja k np.: • P ro je c tS e rv ic e .q u e ry () — służy do pobierania listy projektów. • P r o je c tS e r v ic e .s a v e ({id : 1 5 }, p ro je ctO b j) — aktualizuje projekt o identyfikatorze 15. • P r o je c tS e r v ic e .g e t( { id : 19}) — pobiera projekt o identyfikatorze 19. Jeżeli REST-ow y interfejs API pozwala na więcej niż tylko wykonywanie prostych żądań GET i POST, np. wykonywanie czynności spoza normalnego zakresu A PI typu REST, to m oduł ngResource będzie m ożna nauczyć także tych now ych wywołań. Poniżej przedstawiamy przykład ilustrujący sposób użycia udogodnień oferowanych przez moduł ngResource: < t it le > n g R e s o u r c e E x a m p le < / title > < sty le > .item { p a d d in g: 10px; }

M oduł ngResource

|

117

< / s t y le > < h 1 > W it a jc ie , se rw e ry !< / h 1 > < d iv n g - r e p e a t = "t o d o in m a in C t r l. it e m s " c la s s = "ite m "> < d iv > < sp a n n g -b i n d = "t o d o . la b e l"> < / s p a n > < / d i v> < d iv > - a u t o r: < / s p a n > < / d iv > < d iv > < b u t to n n g - c l i c k = "m a in C trl.d o n e (to d o )"> Z R O B IO N E < / b u tto n > < / d iv >
< in p u t t y p e = "t e x t " p la c e h o l d e r = "E t y k ie t a " ng-m odel="m ai n C t r l. n e w T o d o . la b e l" r e q u ire d > < in p u t t y p e = "t e x t " p la c e h o ld e r = "A u t o r " ng-m odel="m ai n C trl.n e w T o d o .a u t h o r" r e q u ire d > < in p u t t y p e = "s u b m it " v a lu e = "D o d a j " n g -d isa b le d = "a d d F o r m .$ i n v a li d " >
< /d i v> < s c r ip t s r c = " h t t p s : / / a j a x . g o o g le a p i s . c o m / a j a x / lib s / a n g u la r j s / 1 . 3 . 1 1 / a n g u la r . j s "> < / s c r ip t > < s c r i p t s rc = " h t t p s : / / a j a x . g o o g le a p i s . c o m / a j a x / li b s / a n g u la r j s / 1 . 3 . 1 1 / a n g u la r - r e s o u r c e . j s "> < / s c r ip t > < s c r ip t > a n g u la r . m o d u le ( 'n o t e s A p p ', [ 'n g R e s o u r c e '] ) . c o n t r o l l e r ( 'M a i n C t r l ', [ 'N o t e s ', fu n c t io n ( N o t e s ) { var s e lf = t h is ; se l f . i tems = [] ; se lf.n e w T o d o = { } ; v a r fe tc h T o d o s = f u n c t io n ( ) { s e lf . i t e m s = N o t e s . q u e r y () ; }; fe tc h T o d o s(); s e lf . d o n e = f u n c t io n ( n o t e )

{

N o te s.m a rk A sD o n e (n o te , f u n c t io n ( n o t e s ) s e l f . i tems = n o te s;

{

}); }; s e lf . a d d = f u n c t io n ( ) { N o te s .sa v e (se lf.n e w T o d o ).$ p ro m i se .th e n (fe tc h T o d o s) . t h e n ( f u n c t io n ( ) { se lf.n e w T o d o = { } ;

118

|

Rozdział 6. Komunikacja z serwerem przy użyciu usługi $http

}); }; }]) . f a c t o r y ( 'N o t e s ', [ '$ r e s o u r c e ', f u n c t io n ( $ r e s o u r c e ) r e t u r n $ r e s o u r c e ( '/ a p i / n o t e / : i d ', { id : '@ i d '} , { m arkAsDone: { u r l : '/ a p i/ n o t e / : id / d o n e ',

{

method: 'P O S T ', is A r r a y : tru e } }); }]); < / s c r ip t >

Kod ten jest prawie identyczny z wcześniejszym, ale zawiera kilka ważnych zmian: • O prócz rdzennego pliku an gu lar.js do dokumentu in d ex .h tm l dołączyliśmy także plik angular-resource.js. • W bloku angular.m odule dodaliśm y zależność od ngResource, aby m ó c używać zasobów A ngularJS w sw ojej aplikacji. Teraz przyjrzymy się naszej nowej usłudze AngularJS, którą utworzyliśmy za pom ocą modułu ngResource: • Definiujemy nową fabrykę AngularJS o nazwie Notes, która wywołuje usługę $resource i zwraca usługę AngularJS ngResource. Usługa Notes wskazuje końcówkę serwera /api/note/ :id i określa, że cząstka :id jest zm ienną w zdefiniowanym adresie U RL, a więc zm ienia się w zależności od notatki. • Drugi argum ent przekazywany do funkcji $resource umożliwia zdefiniowanie ustawień do­ myślnych dla adresu U R L określonego w pierwszym argumencie. W tym przypadku definiu­ jem y, że jeśli jest to możliwe, funkcja $resource powinna pobrać zm ienną :id z własności id indywidualnego obiektu note. Ponadto w razie potrzeby w tym m iejscu m ożna na sztywno zdefiniować ustawienia domyślne. • Trzeci argum ent funkcji $resource pozwala na zdefiniowanie dodatkowych czynności obok standardowych query, save i get. W tym przypadku dodaliśmy czynność o nazwie markAsDone, będącą żądaniem POST do nieco innego adresu URL, oraz zaznaczyliśmy, że jej wartością zwrotną jest tablica (cała lista notatek). Poza tym w ram ach konfiguracji usługi $resource m ożna określić domyślne param etry zapytania i wiele innych ustawień. Teraz zobaczm y, ja k m ożna wykorzystać tę usługę jak o część naszych kontrolerów: • Aby pobrać listę notatek, należy wywołać funkcję N otes.query() i przypisać ją do s e lf.ite m s . Ciekawe jest to, że podczas gdy przy użyciu modułu ngResource asynchroniczne wywołania m ożem y obsługiwać za pom ocą wywołań zwrotnych lub obietnic, to m ożem y też przypisać odpowiedź bezpośrednio do zm iennej, jeśli nie chcem y wykonywać na niej żadnych dalszych operacji. s e lf .ite m s początkowo będzie pustym obiektem (bądź tablicą), do którego później zostanie wstawiona odpowiedź od serwera.

M oduł ngResource

|

119

• Fun kcja done wywołuje naszą czynność markAsDone z obiektem note i przekazuje wywołanie zwrotne do wykonania w przypadku powodzenia. Jeżeli chodzi o żądania POST, pierwszy ar­ gum ent jest traktowany jako treść żądania, drugi — jako wywołanie zwrotne dla powodzenia, a trzeci — jako wywołanie zwrotne dla błędu. Moduł ngResource automatycznie pobiera war­ tość pola id i wykonuje żądanie do odpowiedniego adresu URL z odpowiednim identyfikatorem. • F u n k cja add zapisuje now y obiekt Todo. O bsługuje ona odpow iedź, w ykorzystując trzeci z m ożliw ych sposobów , czyli obietnicę. Jeśli potrzebna je st obietnica dla asynchronicznego wywołania wykonanego poprzez moduł ngResource, można użyć klucza $promi se z odpowiedzi zwróconej przez fu nkcję i utworzyć norm alny łańcuch wywołań. Jeżeli wywołujemy funkcję N otes.save z obiektem , gdy w obiekcie note je st pole id, to żądanie POST zostanie wykonane z adresem U R L zaw ierającym ten id entyfikator. Jeśli nie, żądanie zostanie w ykonane bez identyfikatora i zostanie utworzony nowy obiekt note. M oduł ngResource to doskonały, prosty dodatek, który pom aga znacznie zm niejszyć ilość po­ trzebnego do napisania kodu. Ale m oduł ten m a też konkretne wym agania dotyczące sposobu działania serwera i udostępniania przez serwer punktów końcow ych. Jeżeli serwer udostępnia R EST-ow y interfejs A PI, to m oduł ngResource jest cennym dodatkiem. Jeśli nie udostępnia, użyj usługi $http i opakuj ją w usługę AngularJS, aby otrzymać podobny interfejs A PI w swoich apli­ kacjach. W ięcej inform acji na tem at konfigurow ania modułu ngResource m ożna znaleźć w ofi­ cjalnej dokum entacji systemu AngularJS, zamieszczonej na stronie poświęconej fabryce $resource (https://docs.an gu larjs.org/api/n gR esou rce/service/$resou rce).

Podsumowanie W rozdziale tym pokazaliśmy, jak wysyłać żądania GET i POST do serwera przy użyciu usługi $http. Usługę tę wykorzystaliśmy do pobierania listy notatek z serwera oraz do wysyłania notatek do zapi­ sania na serwerze. Później zagłębiliśmy się w zagadnienia dotyczące konfiguracji systemu AngularJS oraz przedstawiliśmy techniki zmieniania opcji żądań, takich jak nagłówków czy transformatorów. N astępnie pokazaliśm y, ja k definiow ać domyślne ustawienia dla żądań H T T P oraz ja k i kiedy używać interceptorów i transform atorów . Przedstawiliśmy kilka przykładowych interceptorów , m.in. rejestrujących i autoryzacyjnych. Od tej pory powinieneś być w stanie poradzić sobie z każdym zadaniem polegającym na wykorzystaniu usługi $http. W następnym rozdziale pokazujemy, jak wykonywać testy jednostkowe usług oraz ja k ewentualnie je imitować. Ponadto nauczymy Cię testować usługi i kontrolery wykonujące wywołania serwe­ rowe poprzez ich imitowanie.

120

|

Rozdział 6. Komunikacja z serwerem przy użyciu usługi $http

_____________________________________ROZDZIAŁ 7 .

Testowanie jednostkowe i obiekty XHR

W rozdziałach 5. i 6. pokazaliśmy, ja k wykorzystywać istniejące usługi AngularJS oraz ja k tworzyć własne usługi. Utworzyliśmy kilka prostych przykładów do zapisywania stanu i komunikowania się z różnym i częściami aplikacji oraz kilka usług do kom unikacji z serwerem przez protokół H TTP. W rozdziale 3. przedstawiliśmy m etody jednostkowego testowania kontrolerów. Teraz umiesz już tworzyć własne usługi AngularJS, więc musisz też nauczyć się je testować. W szczególności poka­ żemy Ci, jak testować kontrolery wykorzystujące wbudowane usługi AngularJS, jak również Twoje własne usługi. N a końcu prezentujem y testy jednostkow e dla usług i kontrolerów wykonujących żądania H T T P oraz pokazujemy, jak im itow ać i stosować wstrzykiwanie zależności.

Wstrzykiwanie zależności w testach jednostkowych W rozdziale 3. pokazaliśm y, jak wykorzystać wstrzykiwanie zależności w testach jednostkow ych kontrolerów. Żądaliśmy usługi $ c o n tro lle r, a następnie tworzyliśmy egzemplarze kontrolera zgod­ nie z zapotrzebowaniem. $control l er to usługa AngularJS, której żądamy w teście jednostkowym. Podobnie możemy żądać każdej innej usługi znanej systemowi AngularJS w naszym teście, niezależnie od tego, czy pochodzi ona z rdzenia systemu, czy została utworzona przez nas. AngularJS dowie się, jak ją utworzyć i jakie ma ona zależności, oraz dostarczy nam kompletny egzemplarz do testowania.

Pamiętaj, że aby wykonać te testy, musisz: 1. Przejść do folderu r07. -7 \

2. Wykonać polecenie npm in s ta ll karma karma-jasmine karma-chrome-launcher. 3. Wykonać polecenie karma s ta r t. W ten sposób zainstalujesz wszystkie zależności potrzebne do wykonywania testów jednostkowych Karma. Przykłady i testy opisane w tej książce zostały wykonane przy użyciu narzędzia Karma 0.12.28, systemu AngularJS w wersji 1.3.11 (pliki angular.js i angular-mocks.js) oraz wtyczki karma-jasmine w wersji 2.0. Jeśli podczas wykonywania testów na­ tkniesz się na jakiekolwiek problemy, sprawdź, czy na pewno używasz tych samych wersji oprogramowania.

121

W rozdziale tym korzystamy z następującej konfiguracji narzędzia Karma: // Plik: r07/karm a.conf.js // konfiguracja narzędzia K arm a m o d u le .e x p o rts = f u n c t io n ( c o n f ig ) c o n fig .s e t ( {

{

b a se P a th : 11, fram ew orks: [ 'j a s m i n e '] , file s : [ 'a n g u la r .m i n . j s ', 'a n g u l a r - m o c k s . j s ', '* . j s ' ], excl ude: [ ] , p o r t : 8080, lo g L e v e l: co n fig .L O G _IN FO , autoW atch: tru e , b ro w se rs: [ 'C h r o m e '] , s in g le R u n : f a l s e }); };

Z obaczm y, ja k sprawdzić, czy kontroler przekierow uje użytkownika na now y adres U R L, gdy wywoływana je st w nim funkcja: // Plik: r07/simpleCtrl1.js a n g u la r . m o d u le ( 'n o t e s A p p ', []) . c o n t r o l l e r ( 'S i m p l e C t r l ', [ '$ l o c a t i o n ', v a r s e lf = t h is ; s e l f . n a v ig a t e = f u n c t io n ( ) {

f u n c t io n ( $ lo c a t io n )

{

$ l o c a t i o n . p a t h ( '/ s o m e / w h e re / e lse '); }; }]);

Jest to najprostszy możliwy kontroler zależny od rdzennej usługi systemu AngularJS. Jego jedyną czynnością jest dostarczenie funkcji o nazwie navigate, która zmienia bieżącą lokalizację w przeglą­ darce na /som e/w h ere/else. Teraz spójrzmy, jak może wyglądać test jednostkowy dla tego kontrolera: // Plik: r07/simpleCtrl1Spec.js d e s c r i b e ( 'S i m p l e C t r l ', f u n c t io n ( ) b e fo re E a c h (m o d u le (' n o te sA p p ') ) ;

{

v a r c t r l , $l oc; b e f o r e E a c h ( in j e c t ( f u n c t io n ( $ c o n t r o lle r , c t r l = $ c o n t r o l l e r ( 'S i m p l e C t r l ') ; $ lo c = $ lo c a t io n ;

$ lo c a t io n )

{

})); i t ( 'P o w in ie n p r z e j ś ć na in n ą s t r o n ę . ', $ l o c . p a t h ( '/ h e r e ') ; c t r l. n a v ig a t e () ;

f u n c t io n ( )

e x p e c t ( $ l o c . p a t h ( ) ) . t o E q u a l( '/ so m e / w h e r e / e lse '); }); });

122

|

Rozdział 7. Testowanie jednostkowe i obiekty XHR

{

Jest to prosty test jednostkowy dla kontrolera Simpl e C trl. Sprawdzamy w nim poprawność działania funkcji navigate. Zadaniem tej funkcji jest przekierowanie użytkownika na adres /som e/w h ere/else. Gdyby używano prawdziwej przeglądarki internetow ej, to adres U R L zm ieniłby się w je j pasku adresu i nastąpiłoby rzeczywiste przejście na nową stronę. Ale w teście jednostkowym nie chcemy, aby tak się stało, więc m ożem y wykorzystać im itacje usług typu $ lo c a tio n czy $window, dostępne w pliku a n g u lar-m ock s.js, który dołączyliśmy w ram ach konfiguracji Karm y. D zięki temu może­ my wykonywać testy jednostkow e bez przejm owania się przeglądarką i innym i rzeczami, takimi ja k stan globalny, które mogłyby m ieć wpływ na wynik testów. Im itacja usługi $ lo ca tio n (dla której dzięki wstrzykiwaniu zależności nie m usieliśm y zm ieniać ani linijki kodu produkcyjnego!) umożliwia ustawienie początkowego stanu lokalizacji przeglą­ darki (w tym przypadku /h e re). Po wykonaniu funkcji c t r l.n a v ig a t e ( ) m ożem y określić oczeki­ wanie, że wartość $ lo c a tio n .p a th zm ieni się na /some/where/else. Nie spowoduje to zm iany rze­ czywistego adresu U R L w przeglądarce, więc testy jednostkow e zakończą się norm alnie.

Przechowywanie stanu między testami jednostkowymi Teraz zm odyfikujem y poprzedni kod i testy jednostkow e w ten sposób, że dodamy dwie funkcje i dwa testy, m ające na celu zadem onstrowanie sposobu przenoszenia (albo nieprzenoszenia) sta­ nu między testam i: / / Plik: r07/simpleCtrl2.js a n g u la r . m o d u le ( 's im p le C t r l 2 A p p ', [ ]) . c o n t r o l l e r ( 'S i m p l e C t r l 2 ', [ '$ l o c a t i o n ', f u n c t io n ( $ lo c a t io n , $window) { var s e lf = t h is ; s e lf . n a v ig a t e 1 = f u n c t io n ( ) { $ lo c a t io n . p a t h ( '/ s o m e / w h e r e ') ;

'$ w in d o w ',

}; s e lf . n a v ig a t e 2 = f u n c t io n ( ) { $ lo c a tio n .p a th ('/ s o m e / w h e r e / e l s e ') ; }; }]);

/ / Plik: r07/simpleCtrl2Spec.js d e s c r i b e ( 'S i m p l e C t r l 2 ', f u n c t io n ( ) { b e fo r e E a c h ( m o d u le ( 's im p le C tr l 2A p p ') ) ; v a r c t r l , $l oc; b e f o r e E a c h ( in j e c t ( f u n c t io n ( $ c o n t r o lle r , $ lo c a t io n ) c t r l = $ c o n t r o l l e r ( 'S i m p l e C t r l 2 ') ; $ lo c = $ lo c a t io n ;

{

})); i t ( 'P o w in i e n p r z e j ś ć na in n ą s t r o n ę . ', f u n c t io n ( ) e x p e c t ( $ l o c . p a t h ( ) ) . t o E q u a l ( ' ') ; $ l o c . p a t h ( '/ h e r e ') ; c t r l. n a v ig a t e 1 (); e x p e c t ( $ l o c . p a t h ( ) ) . t o E q u a l( '/som e/w here') ;

{

}); i t ( 'P o w i n i e n p r z e j ś ć na in n ą s t r o n ę ',

f u n c t io n ( )

{

Wstrzykiwanie zależności w testach jednostkowych

|

123

e x p e c t ( $ l o c . p a t h ( ) ) . toEqual $ l o c . p a t h ( '/ t h e r e ') ; c t rl.n a v ig a t e 2 (); e x p e c t ( $ l o c . p a t h ( ) ) . t o E q u a l( '/ s o m e / w h e r e / e lse '); }); });

Dodaliśm y do kontrolera dwie funkcje — n a v ig a te l i navigate2 — powodujące przejście pod określony adres URL. Jeśli najpierw wywołamy funkcję n av ig atel, to przeniesie nas ona pod adres /so m e/w h e re. A jeśli po niej wywołamy funkcję navigate2, to zostaniem y przeniesieni pod adres /som e/w h ere/else. Zmiana dokonana przez funkcję nav ig atel (przekierowanie na inny adres URL) jest widoczna na początku wywołania funkcji navigate2. Teraz w tym kontekście spójrzm y na nasze testy jednostkowe. M am y dwa testy — po jednym dla każdej funkcji. Każdy z tych testów sprawdza, czy po wywołaniu funkcji ścieżka $ lo ca t ion zmienia się na właściwy adres URL. W arto zwrócić uwagę na test wykonywany przed wywołaniem funkcji. W obu przypadkach spodziewamy się, że adres U RL w przeglądarce jest pustym łańcuchem . T ak i kod m ożem y pisać, poniew aż w teście jed nostkow ym nie m a żadnego globalnego stanu. Jeżeli wykonamy pierwszy test w prawdziwej aplikacji, to zm ieni on wartość adresu U RL w prze­ glądarce. Z m iana ta byłaby w idoczna dla drugiego testu. To sprawia, że kolejność wykonywania testów jest ważna, gdyż składnik zm ieniający w artość zm iennej używanej przez inne testy może decydować o powodzeniu lub niepowodzeniu tych testów. W system ie A ngularJS unika się takich sytuacji poprzez pozbycie się globalnego stanu z testów jednostkow ych. Usługa $l o ca tio n jest niszczona i tw orzona w każdym teście osobno. W szystko to dzieje się, bo przed każdym testem jednostkow ym tworzymy egzemplarz modułu. Egzemplarz ten m a za zadanie utworzenie świeżej wersji każdej usługi używanej przez nasze testy.

Testowanie usług W poprzednich podrozdziałach pokazaliśm y, ja k wstrzykiwać usługi A ngularJS do testów je d ­ nostkowych. M echanizm wstrzykiwania zależności i system AngularJS zajm ują się też autom a­ tycznie obsługą stanu m iędzy testam i, aby każdem u testow i zapew nić niezależn ość oraz aby zm iany wprowadzone przez jeden test nie zaburzały pracy innych. W tym podrozdziale najp ierw pokażem y C i, ja k przetestow ać w łasnoręcznie napisaną usługę, a następnie przedstawim y sposoby tw orzenia je j im itacji. Spójrzm y jeszcze raz na prostą usługę Item Service z rozdziału 5.: // Plik: r07/notesA pp1js a n g u la r . m o d u le ( 'n o t e s A p p 1 ', [ ]) . f a c t o r y ( 'I t e m S e r v i c e ', [ f u n c t io n ( ) v a r i tems = [ { id : 1, la b e l: 'Ele m en t 0 '} , { id : 2, la b e l: 'Ele m en t 1 '} ]; retu rn { l i s t : f u n c t io n ( ) r e t u r n item s;

{

{

},

124

|

Rozdział 7. Testowanie jednostkowe i obiekty XHR

add: f u n c t io n ( it e m ) { i te m s .p u s h (i tem ); } }; }]) . c o n t r o l l e r ( 'I t e m C t r l ', [ 'I t e m S e r v i c e ', var s e lf = t h is ; s e lf . i t e m s = I t e m S e r v i c e . l i s t ( ) ;

f u n c t io n ( It e m S e r v ic e )

{

}]);

W kodzie tym wykorzystaliśmy usługę Item Service, którą utworzyliśmy w rozdziale 5, oraz zde­ finiowaliśm y bardzo prosty kontroler pobierający listę elem entów podczas ładowania. Przypom i­ namy, że bez względu na to, ja k nasza usługa jest zdefiniowana (fa c to ry , s e rv ic e lub provider), w k ontrolerach i testach używa się je j zawsze tak sam o. O znacza to, że wszystkie rodzaje usług AngularJS testuje się w taki sam sposób. Spójrz na test jednostkowy powyższej usługi: / / Plik: r07/notesApp1ServiceSpec.js d e s c r i b e ( 'It e m C t r l ze ś r ó d lin io w ą im i t a c j ą ', b e f o r e E a c h ( m o d u le ( 'n o t e s A p p l') ) ;

f u n c t io n ( )

{

v a r s e r v ic e ; b e fo r e E a c h (i n j e c t ( f u n c t i o n (Ite m S e r v i ce) s e r v ic e = It e m S e rv ic e ;

{

})); it ( 'p o w in n a zw racać dom yślne e le m e n t y ', f u n c t io n ( ) e x p e c t ( s e r v i c e . l i s t ( ) ) . t o E q u a l( [ { id : 1,la b e l: 'E le m e n t 0 '} , { id : 2, la b e l: 'E le m e n t 1 '} ]);

{

}); it ( 'p o w in n a dodawać e le m e n t y ', f u n c t io n ( ) { v a r newItem = { id : 3, la b e l: 'Nowy e le m e n t '} ; s e rv ic e . a d d ( n e w Ite m ) ; e x p e c t(se rv i c e . lis t ( ) ) . t o E q u a l ( [ { id : 1,la b e l: 'E le m e n t 0 '} , { id : 2, la b e l: 'E le m e n t 1 '} , newItem ]); }); });

Powyższy kod zawiera dwa testy jednostkow e — jed en dla funkcji wyświetlającej listę elementów, a drugi dla funkcji dodającej elementy. Gdy testuje się usługi, zamiast wstrzykiwać usługę $control l er w celu utworzenia egzemplarza kontrolera, m ożemy bezpośrednio wstrzyknąć potrzebną nam usługę. W tym przypadku określiliśm y Item Service jako usługę do wstrzyknięcia do beforeEach i zapisaliśmy ją w lokalnej zm iennej se rv ice . T est fu n k cji l i s t polega na je j w yw ołaniu i spraw dzeniu, czy zw rócone w artości zgadzają się z domyślnymi w usłudze. N atom iast w teście funkcji add dodajemy nowy elem ent i sprawdzamy, czy lista zwracana przez usługę zawiera ten nowy elem ent obok domyślnych.

Testowanie usług

|

125

Jako że wszystkie usługi są tworzone w każdym teście jednostkow ym od nowa, kolejność wyko­ nania powyższych dwóch testów nie m a znaczenia dla wyników. To znaczy, że gdyby najpierw wykonano test dodający element, w następnym teście i tak obecne byłyby tylko elementy domyślne, bez tego nowego. W następnym podrozdziale pokazujemy, ja k im itować usługi w sytuacjach, gdy nie chcem y te­ stować ich prawdziwej funkcjonalności.

Imitowanie usług Co zrobić, kiedy m am y usługę bardzo złożoną albo taką, której nie chcem y testować? W iesz już, że plik atrap systemu AngularJS zawiera m.in. im itacje usług $ lo c a tio n i $window. W tym pod­ rozdziale dowiesz się jeszcze, ja k tworzyć własne atrapy usług. Na potrzeby naszego testu jednostkowego utworzymy im itację usługi Item Service z poprzednie­ go podrozdziału, aby m óc przesłonić je j domyślną im plem entację. Zadanie to m ożna zrealizować na dwa sposoby. Pierw sza m etod a polega na p rz esło n ięciu usługi w teście jed n o stk o w y m przy użyciu atrapy śród lin iow ej: // Plik: r07/notesApp1Spec.js d e s c r i b e ( 'K o n t r o l e r Ite m C trl ze ś r ó d lin io w ą i m i t a c j ą ', b e fo re E a c h (m o d u le (' n o t e s A p p 1 ') ) ;

f u n c t io n ( )

{

v a r c t r l , m o ck Se rvic e ; b e fo re E a c h (m o d u le (fu n c ti o n ($ p r o v i de) { m o ck Se rvic e = { l i s t : f u n c t io n ( ) { r e t u r n [ { i d : 1, la b e l: 'I m i t a c j a '} ] ; } }; $ p r o v id e . v a lu e ( 'It e m S e r v i c e ' , m o c k S e rv ic e ); })); b e fo r e E a c h (i n j e c t ( f u n c t i o n ( $ c o n t r o lle r ) c t r l = $ c o n t r o l l e r ( 'I t e m C t r l ') ;

{

})); i t ( 'P o w in ie n załadow ać im it a c j e e le m e n tó w .', f u n c t io n ( ) { e x p e c t ( c t r l . i t e m s ) . t o E q u a l ( [ { i d : 1, la b e l: 'I m i t a c j a '} ] ) ; }); });

Początek tego testu je st podobny ja k początek poprzedniego — tw orzymy egzem plarz m odułu notesAppł. Dalej użyliśmy kolejnego bloku beforeEach, w którym przesłaniamy usługę Item Service je j im itacją. Zastosow aliśm y funkcję module, ale zamiast nazwy modułu przekazaliśmy je j funkcję, której wstrzyknęliśmy dostawcę $provide. Dostawca ten dzieli swoją przestrzeń nazw z wcześniej załadow anym i m odu łam i. Z atem teraz tw orzym y naszą im ita cję m ockService i in form u jem y

126

|

Rozdział 7. Testowanie jednostkowe i obiekty XHR

dostawcę, że gdy jakikolw iek kontroler lub jakakolw iek usługa zażąda usługi Item Service, należy zamiast niej przekazać naszą wartość. Jako że robim y to p o załadowaniu modułu notesAppl, ory­ ginalna definicja Item Service zostaje przesłonięta. Reszta tego testu wygląda tak samo jak wcześniej, z tym wyjątkiem, że teraz sprawdzamy, czy war­ tość tablicy items w kontrolerze jest zwracana przez naszą im itację zamiast przez oryginalną usługę. Druga m etoda przesłaniania usług polega na zrobieniu tego na poziom ie globalnym, a nie p o je­ dynczego testu. W ybór między techniką polegającą na utworzeniu im itacji przy użyciu zm iennej lokalnej i funkcji $provide.value (jak w poprzednim przykładzie) a m etodą globalną (stosowaną w systemie AngularJS) zależy od tego, czy chcemy, by nasza im itacja była dostępna także dla innych testów, czy nie. Atrapa, którą utworzyliśmy wcześniej, jest dostępna do użytku tylko w jednym konkretnym bloku — d e scrib e . Jeśli chcesz rozszerzyć zakres je j zastosow ań na bardziej ogólny, m ożesz to zrobić w następujący sposób: / / Plik: r07/notesApp1-mocks.js a n g u la r . m o d u le ( 'n o t e s A p p lM o c k s ', [ ]) . f a c t o r y ( 'I t e m S e r v i c e ', [ f u n c t io n ( ) retu rn { l i s t : f u n c t io n ( ) { r e t u r n [ { i d : 1, la b e l:

{

'I m i t a c j a '} ] ;

} }; }]);

T o, co w cześniej zakodow aliśm y na sztywno w usłudze m ockService, zostało przeniesione do usługi o tej sam ej nazwie, ale w innym module, o nazwie notesApplMocks. Plik ten będzie zapisany w folderze testowym i dołączany przez plik k a rm a .co n f.js, lecz nie w aplikacji produkcyjnej. Teraz nasze testy powinny wyglądać tak: / / Plik: r07/notesApp1Spec WithMock.js d e s c r i b e ( 'K o n t r o l e r Ite m C trl

z g lo b a ln ą i m i t a c j ą ',

f u n c t io n ( )

{

var c t rl; b e f o r e E a c h ( m o d u le ( 'n o t e s A p p l') ) ; b e fo r e E a c h (m o d u le ('n o t e sA p p lM o c k s ') ) ; b e f o r e E a c h ( in j e c t ( f u n c t io n ( $ c o n t r o l l e r ) c t r l = $ c o n t r o l l e r ( 'I t e m C t r l ') ;

{

})); i t ( 'P o w in i e n załadow ać im it a c j e e le m e n tó w .', e x p e c t ( c t r l . i t e m s ) . t o E q u a l ( [ { i d : l , la b e l:

f u n c t io n ( ) { 'I m i t a c j a '} ] ) ;

}); });

W kodzie tym po module notesAppl zostanie załadowany m oduł notesApplMocks, przesłaniający usługę Item Service. Następnie, gdy test załaduje kontroler, który później wywołuje usługę, zostaje wykorzystana utworzona przez nas im itacja usługi Item Service.

Testowanie usług

|

127

Technikę tę m ożna zastosować, kiedy potrzebna jest globalna im itacja wielokrotnego użytku oraz chcemy skorzystać z atrapy na poziom ie bloku d escrib e dla jednego konkretnego testu.

Szpiedzy A co, gdybyśm y nie ch cieli zaim plem entow ać im ita cji całej usługi? I gdybyśm y w przypadku usługi Item Service chcieli się tylko dowiedzieć, czy funkcja l i s t została wywołana, i nie przej­ m ow ać się zw róconą przez nią wartością? W takich sytuacjach pom ocni są szpiedzy Jasm in e (ang. spy). Szpieg umożliwia podłączenie się do wybranych funkcji i sprawdzenie, czy zostały wy­ wołane, ile razy zostały wywołane, jakie argum enty im przekazano itd. Poniżej przedstawiamy nasz test z wykorzystaniem szpiegów: // Plik: r07/notesApp1SpecWithSpies.js d e s c r i b e ( 'K o n t r o l e r Ite m C trl ze s z p ie g a m i', b e fo re E a c h (m o d u le (' n o t e s A p p l ') ) ;

f u n c t io n ( )

{

b e f o r e E a c h ( in j e c t ( f u n c t io n ( $ c o n t r o lle r , It e m S e rv ic e ) sp y O n (Ite m S e r v ic e , 'l i s t ') . a n d . c a l l T h r o u g h ( ) ; it e m S e rv ic e = It e m S e rv ic e ; c t r l = $ c o n t r o l l e r ( 'I t e m C t r l ') ;

{

var c t rl,

it e m S e rv ic e ;

})); i t ( 'P o w in ie n ładow ać im it a c j e e le m e n tów ', f u n c t io n ( ) e x p e c t ( i tem Servi c e . lis t ) . t o H a v e B e e n C a lle d ( ) ; e x p e c t ( i tem Servi c e . l i s t . c a l l s . c o u n t ( ) ) . t o E q u a l ( 1 ) ; e x p e c t ( c t r l . i t e m s ) . t o E q u a l( [ { id : 1, la b e l: { id : 2, la b e l:

{

'Ele m en t 0 '} , 'Ele m en t 1 '}

]); }); });

Pierwszym argum entem funkcji spyOn powinien być obiekt, a drugim — łańcuch reprezentujący nazwę fu nkcji, którą chcem y szpiegować. W tym przykładzie nakazaliśm y system owi Jasm ine szpiegowanie funkcji l i s t z usługi Item Service. Ponadto za pom ocą wywołania and.callThrough() zaznaczyliśmy, że chcemy, aby sama usługa też była wywoływana. Dzięki temu za pom ocą Jasmine m ożna sprawdzić, czy dana funkcja jest wywoływana, i jednocześnie pozwolić je j działać w n o r­ malny sposób. Jest to opakowanie dla istniejącej funkcji Ite m S e rv ic e .lis t. Jasmine pozwala istniejącem u kodowi działać norm alnie i zarazem daje nam wgląd w to, co się dzieje, oraz inform uje nas, czy wywoły­ wane są odpowiednie funkcje. Dane są zwracane z oryginalnej usługi, ja k widać w bloku expect dotyczącym elem entów kontrolera. Należy podkreślić, że zaleca się skonfigurow anie wszystkich im itacji i szpiegów przed zainicjow aniem kontrolerów. A co, gdybyśmy chcieli, by istn iejąca m etoda nie była wykonywana norm alnie? Zobaczm y, ja k przesłonić wybraną m etodę za pom ocą szpiegów:

128

|

Rozdział 7. Testowanie jednostkowe i obiekty XHR

/ / Plik: r07/notesApp1SpecWithSpyReturn.js d e s c r i b e ( 'It e m C t r l z S p y R e t u r n ', f u n c t io n ( ) b e f o r e E a c h ( m o d u le ( 'n o t e s A p p l') ) ; var c t r l,

it e m S e rv ic e ;

b e f o r e E a c h ( in j e c t ( f u n c t io n ( $ c o n t r o lle r , s p y O n (Ite m S e rv ic e , ' l i s t ' ) . a n d . r e t u r n V a lu e ( [ { id : it e m S e rv ic e = It e m S e rv ic e ; c trl

{

1, la b e l:

It e m S e rv ic e )

{

'I m i t a c j a '} ] ) ;

= $ c o n t r o l l e r ( 'I t e m C t r l ') ;

})); i t ( 'P o w i n i e n załadow ać im it a c j e e le m e n tó w .', f u n c t io n ( ) { e x p e c t ( i tem Servi c e . lis t ). t o H a v e B e e n C a l l e d ( ) ; e x p e c t ( i tem Servi c e . l i s t . c a l l s . c o u n t ( ) ) . t o E q u a l ( 1 ) ; e x p e c t ( c t r l . i t e m s ) . t o E q u a l ( [ { i d : 1, la b e l: 'I m i t a c j a '} ] ) ; }); });

W przykładzie tym przesłoniliśmy metodę l i s t usługi It e m S e r v i c e i zastąpiliśmy ją naszym szpie­ giem Jasmine. Funkcja sp y O n zwraca szpiega, który jest wywoływany z funkcją a n d . r e t u r n V a l u e na szpiegu utworzonym przez funkcję c r e a t e S p y , i przekazuje m u wartość do zwrócenia. Zauważ, że opisywany kod znajduje się przed utworzeniem kontrolera, co jest z a le c a n e . Następnie w teście jednostkow ym m ożem y sprawdzić, czy funkcja I t e m S e r v i c e . l i s t została wywołana i czy wywo­ łano ją jed en raz. Ponadto nasz szpieg zwraca wartość w tablicy it e m s kontrolera (określoną przez a n d .r e t u r n V a lu e

na funkcji c r e a t e S p y ).

Testowanie jednostkowe wywołań serwerowych W iesz już, ja k testow ać proste usługi oraz ja k tworzyć im itacje usług i fu nkcji w zależności od potrzeb. W yposażony w tę wiedzę możesz przejść do testow ania kontrolerów i usług wykorzy­ stujących usługę $ h t t p do wykonywania wywołań serwerowych. W testach jednostkow ych koncentrujem y się na sprawdzaniu działania pojedynczych jednostek kodu we wszystkich warunkach. W testach takich często trzeba użyć atrapy większego systemu, który bierze udział w procesie, np. serwera, jak iejś zewnętrznej zależności, struktury D O M lub przeglądarki itd. Jeśli korzystasz z systemu AngularJS i dołączysz plik an g u lar-m ocks.js do konfiguracji narzędzia Karm a, system ten automatycznie zadba o to, by w testach jednostkow ych usługa $ h t t p nie wy­ konyw ała prawdziwych wywołań serwerowych. W szystkie takie wywołania są przechwytywane i m ożna je przetestow ać w kontekście testu jednostkow ego. To znacznie przyspiesza testy i za­ pewnia im stabilność. Poniżej znajduje się przykładowy kontroler wykonujący wywołania serwerowe za pom ocą usługi $ h ttp .

Zastanowimy się, ja k go przetestować.

Testowanie jednostkowe wywołań serwerowych

|

129

// Plik: r07/serverApp.js a n g u la r . m o d u le ( 's e r v e r A p p ',

[ ])

. c o n t r o l l e r ( 'M a i n C t r l ', [ '$ h t t p ', v a r s e lf = t h is ; s e l f . item s = [ ]; s e lf . e r r o r M e s s a g e = ' ' ;

f u n c t io n ( $ h t t p )

$ h t t p . g e t ( '/ a p i / n o t e ') . t h e n (f u n c t i o n (re sp o n se ) s e lf . i t e m s = r e s p o n s e .d a t a ; }, fu n c t io n ( e r r R e s p o n s e ) { s e lf . e r r o r M e s s a g e = e rrR e sp o n se .d a ta .m sg ;

{

{

}); }]);

Kontroler ten podczas ładowania wysyła żądanie GET do /api/note i zapisuje odpowiedź w tablicy items. Jeśli wystąpi błąd, to w egzemplarzu kontrolera zostaje zapisana wiadomość o błędzie. Teraz spójrzm y na test, za pom ocą którego m ożna sprawdzić ten kontroler: // Plik: r07/serverAppSpec.js d e s c r ib e ( 'W y w o ła n ia serwerowe M a i n C t r l ', b e fo re E a c h (m o d u le (' s e r v e r A p p ') ) ;

f u n c t io n ( )

{

v a r c t r l , mockBackend; b e f o r e E a c h ( in j e c t ( f u n c t io n ( $ c o n t r o lle r ,

$h ttp Ba ckend )

{

mockBackend = $h ttp Ba cken d ; m o c k B a c k e n d .e x p e c tG E T ('/ a p i/ n o te ') . r e s p o n d ( [ { id : 1, la b e l: 'I m i t a c j a '} ] ) ; c t r l = $ c o n t r o l l e r ( 'M a i n C t r l ') ;

/ / w tym miejscu żądanie do serwera jest ju ż wysłane })); i t ( 'P o w in ie n ładow ać elem enty z s e r w e r a . ',

f u n c t io n ( )

{

/ / Początkowo, przed nadejściem odpowiedzi od serwera, tablica items pow inna być pusta. e x p e c t ( c t r l. it e m s ). t o E q u a l( [ ]) ;

/ / symulacja odpowiedzi od serwera m o c k B a c k e n d . flu s h () ; e x p e c t ( c t rl. it e m s ). t o E q u a l( [ {id :

1, la b e l:

'I m i t a c j a '} ] ) ;

}); a f t e r E a c h ( f u n c t io n ( )

{

/ / upewnienie się, że wszystkie bloki expect dla $httpBackend zostały wykonane m o ckBacke n d .ve ri fy N o O u tsta n d i n g E x p e c ta ti o n ( );

/ / upewnienie się, że na wszystkie żądan ia do serwera nadeszła odpow iedź (przy użyciu fu n kcji flush()) m o ckBacke n d .ve ri fy N o O u ts ta n d in g R e q u e st( ); }); });

D o przetestow ania naszego kontrolera w ykonującego wywołania X H R wykorzystaliśmy usługę o nazwie $httpBackend. Usługa $ h ttp używa je j wew nętrznie do wysyłania żądań X H R . W pliku angular-m ocks.js znajduje się imitacja usługi $httpBackend, udaremniająca rzeczywiste wywołania do serwera i dająca programiście punkty zaczepienia do ustawiania oczekiwań i włączania odpowiedzi.

130

|

Rozdział 7. Testowanie jednostkowe i obiekty XHR

W bloku b e f o r e E a c h zażądaliśmy wstrzyknięcia usługi $ h t t p B a c k e n d do testu. Jako że nasz kon ­ troler wykonuje wywołanie serwerowe w ram ach operacji ładowania, oczekiwania dotyczące wy­ wołań serwerowych musimy ustawić przed zainicjow aniem kontrolera. O czekiwania dotyczące wywołań serwerowych na usłudze $ h t t p B a c k e n d m ożna ustawić na dwa sposoby: expect

Funkcja e x p e c t pozwala dokładnie określić, ile żądań zostanie wykonanych i do jak ich adre­ sów U R L zostaną one wysłane, oraz umożliwia kontrolow anie odpowiedzi. Funkcja ta wystę­ puje w wielu wersjach, po jednej dla każdej m etody H T T P , np. e x p e c t G E T lub e x p e c t P O S T . Jej pierwszy argum ent jest adresem U RL, a drugi — jeśli zostanie zdefiniowany — określa dane P O ST . GET

Zatem wywołanie e x p e c t G e t ( 'a p i / n o t e s ') w poprzednim przykładzie oznacza żądanie

do podanego adresu URL. Analogicznie wywołanie e x p e c t P O S T ( '/ a p i / n o t e s ',

'C z e ś ć ' } )

{ la b e l:

nakazuje usłudze spodziewać się żądania P O S T , którego dane pow inny dokładnie

odpowiadać tem u, co zostało przekazane w drugim argumencie. when

Ta funkcja jest podobna do e x p e c t pod tym względem, że również pobiera adres U R L i po­ ten cjaln e dane P O S T . Składnię też m a dokładnie taką sam ą. R óżn ica polega na tym , że dla funkcji when nie m a znaczenia kolejność żądań ani to, ile razy dane wywołanie zostało wyko­ nane. Funkcja ta po prostu wykrywa żądanie i wysyła odpowiedź. W funkcji e x p e c t wynik testu m oże być negatywny, jeśli oczekiwanie nie zostanie spełnione. N atom iast w funkcji when test zakończy się pozytywnie nawet wtedy, gdy test w ogóle nie wykona wywołania. W istocie różnica sprowadza się do tego, że funkcja e x p e c t jest bardziej precyzyjna i pozwala na określanie oczekiwań. Z kolei funkcja when tworzy namiastkę zaplecza (nam iastka, ang. stu b, to coś, co zwraca tę sam ą odpowiedź bez względu na żądanie) um ożliw iającą zwracanie jednolitej odpowiedzi bez jakichkolwiek oczekiwań w odniesieniu do żądań. Gdy zostanie użyta jed na z wersji funkcji e x p e c t (np. e x p e c t G E T ) bądź w he n , m ożna zdefiniować odpowiedź dla tego wywołania serwerowego przez dołączenie do niego funkcji r e s p o n d . Jeżeli fu n k cji tej zostanie przekazany jed en argu m ent, je s t on traktow any ja k o odpow iedź serwera. Ewentualnie m ożna też przekazać dwa argumenty i wówczas pierwszy będzie oznaczał kod statusu, a drugi będzie treścią odpowiedzi (np. r e s p o n d ( 4 0 4 ,

{m sg :

' Ź l e ' } ) ).

W naszym przykładzie w od­

powiedzi przesyłamy listę elementów z serwera. Teraz przechodzimy do samego testu. Podczas ładowania kontrolera następuje inicjacja pustej tablicy it e m s.

W pierwszym bloku e x p e c t sprawdzamy, czy tablica it e m s jest właśnie pusta. W prawdzi­

wej aplikacji działającej w produkcji najpierw wysyłane jest żądanie do serwera, a następnie nad­ chodzi odpowiedź. Żądania serwerowe są z natury asynchroniczne. Do sym ulacji tego w systemie AngularJS służy m etoda f l u s h usługi zaplecza. Z atem gdy zostanie wysłane żądanie do serwera, dom yślnie system AngularJS porów nuje je ze zdefiniow anym i oczekiw aniam i i podpina się do niego bez zw racania odpowiedzi. N astępnie, kiedy program ista wywoła fu nkcję f l u s h ( ) na $ h t t p B a c k e n d , AngularJS wysyła odpowiedzi dla wszystkich żądań, które klient odebrał do tej pory.

Testowanie jednostkowe wywołań serwerowych

|

131

Funkcja f l u s h ( ) umożliwia testowanie asynchronicznych zachowań bez pisania asynchronicznych testów. Ponadto funkcja ta $ h t t p B a c k e n d . f l u s h ( ) przyjmuje jako argument liczbę całkowitą infor­ m ującą imitację zaplecza, ile żądań serwerowych ma zwrócić. Jest to przydatne, gdy chcemy spraw­ dzić, czy kontroler realizuje cztery wywołania serwerowe, ale część pracy wykonuje dopiero po otrzymaniu odpowiedzi dla przynajmniej trzech z nich. W takim przypadku m ożna zrzucić żądania pojedynczo (przy użyciu wywołania $ h t t p B a c k e n d . f l u s h ( ł ) ) lub trzy naraz ( $ h t t p B a c k e n d . f l u s h ( 3 ) ). W tym m om encie m ożem y sprawdzić, czy dane zwrócone przez serwer zostały zapisane w odpo­ wiedniej zm iennej w kontrolerze (w tablicy it e m s ). D obrą i z a le c a n ą praktyką pisania testów za pom ocą usługi $ h t t p B a c k e n d je st dodanie bloku a fte rE a c h .

W poprzednim przykładzie należy go dodać dla dwóch wywołań funkcji:

• Pierwsza funkcja, v e r i f y N o O u t s t a n d i n g E x p e c t a t i o n s ( ) , sprawdza, czy wszystkie oczekiwania dotyczące usługi $ h t t p B a c k e n d zostały zaspokojone. Jeśli więc dodano kolejne oczekiwanie, ale kontroler nigdy nie wykonał takiego wywołania serwerowego, w ynik testu będzie nega­ tywny. Jest to dobry sposób na upewnienie się, że program robi to, co chcemy. • Druga funkcja, v e r i f y N o O u t s t a n d i n g R e q u e s t s ( ) , pozwala sprawdzić, czy w pełni przetestowano wszystkie przypadki. Jak wspomnieliśm y wcześniej, system AngularJS dzieli wszystkie żąda­ nia serwerowe na żądanie i odpowiedź. Odpowiedzi wyzwalamy za pom ocą funkcji f l u s h ( ) . Funkcja v e r i f y N o O u t s t a n d i n g R e q u e s t s pozwala się upewnić, że dla każdego wykonanego wy­ wołania serwerowego funkcja f l u s h ( ) wyzwoliła odpowiedź. Jeśli nie, wynik testu jest negatywny.

Testy integracyjne C o, jeśli stosu jąc najlepsze praktyki, wyrzucim y wywołanie $ h t t p z kontrolera? Zam iast tego przeniesiem y je do usługi N o t e S e r v i c e , którą kontroler oddelegowuje do pobierania listy notatek. W takim przypadku m am y dwie możliwości: • Po pierwsze m ożem y napisać skoncentrow any test jednostkow y i im itow ać (lub szpiegować) usługę N o t e S e r v i c e oraz upew nić się, czy nasz kontroler oddelegowuje zadania do od po­ wiednich interfejsów A PI i czy przepływ sterowania oraz argum enty są prawidłowe. • Po drugie m ożemy napisać test jednostkowy na poziomie integracyjnym skoncentrowany tylko na im itowaniu zaplecza (za pom ocą usługi $ h t t p B a c k e n d ) i sprawdzaniu całego przepływu sterowania. Spróbujem y drugiego z wym ienionych rozwiązań: // Plik: r07/serverAppWithService.js a n g u la r . m o d u le ( 's e r v e r A p p 2 ', []) . c o n t r o l l e r ( 'M a i n C t r l ', [ 'N o t e S e r v i c e ', v a r s e lf = t h is ; s e l f . item s = [ ]; s e lf . e r r o r M e s s a g e = ' ' ;

fu n c t io n ( N o t e S e r v ic e )

N o t e S e r v ic e . q u e r y ( ) . t h e n ( f u n c t io n ( r e s p o n s e ) s e lf . i t e m s = r e s p o n s e .d a t a ; }, fu n c t io n ( e r r R e s p o n s e ) {

132

|

Rozdział 7. Testowanie jednostkowe i obiekty XHR

{

{

s e lf . e r r o r M e s s a g e = e rrR e sp o n se .d a ta .m sg ; }); }]) . f a c t o r y ( 'N o t e S e r v i c e ', [ '$ h t t p ', retu rn { q u e ry : f u n c t io n ( ) {

f u n c t io n ( $ h t t p )

{

r e t u r n $ h t t p . g e t ( '/ a p i / n o t e ') ; } }; }]);

T en przykład jest prawie taki sam ja k ten przedstawiony w poprzednim podrozdziale. Różnica polega na tym, że wywołanie $h ttp przeniesiono do usługi N oteService. Pod względem fu n k cjo­ nalnym nie m a żadnej różnicy. Teraz zobaczmy, ja k to przetestować: / / Plik: r07/serverAppWithServiceSpec.js d e s c r i b e ( 'I n t e g r a c j a a p l i k a c j i se rw e ro w e j', b e fo re E a c h (m o d u le (' s e rv e rA p p 2 ') ) ;

f u n c t io n ( )

{

v a r c t r l , m ockBackend; b e f o r e E a c h ( in j e c t ( f u n c t io n ( $ c o n t r o lle r , $h ttp Ba ckend )

{

mockBackend = $h ttp Ba cken d ; m o ckBacke n d .expe ctG ET ('/ a p i / n o t e ') .re sp o n d (4 0 4 , {msg: 'N ie z n a l e z i o n o '} ) ; c t r l = $ c o n t r o l l e r ( 'M a i n C t r l ') ;

// w tym miejscu wywołanie serwerowe zostało ju ż wykonane })); i t ( 'P o w in i e n o b s łu ż y ć b łą d pod czas ład o w an ia e le m e n tó w .', f u n c t io n ( )

{

/ / początkow o, zanim nadejdzie odpow iedź od serwera, tablica items pow inna być pusta e x p e c t ( c t r l . i tem s). t o E q u a l( [ ] ) ;

// symulacja odpowiedzi serwera m o c k B a c k e n d .flu s h ();

// brak elem entów od serwera, tylko błąd // tablica items nadal pow inna być pusta e x p e c t ( c t r l . i tem s). t o E q u a l( [ ] ) ;

/ / sprawdzenie w iadom ości o błędzie e x p e c t ( c t r l . e r r o r M e s s a g e ) . t o E q u a l ( 'N i e z n a l e z io n o ') ; }); a f t e r E a c h ( f u n c t io n ( )

{

// sprawdzenie, czy wszystkie oczekiw ania dotyczące $httpBackend zostały wywołane m o ckBacke n d .ve ri fy N o O u ts ta n d in g E x p e c ta ti o n ( );

// sprawdzenie, czy na wszystkie żądania do serwera nadeszła odpow iedź (przy użyciu fu n kcji flush()) m o c k B a c k e n d .v e rify N o O u tsta n d in g R e q u e st() ; }); });

Testowanie jednostkowe wywołań serwerowych

|

133

W tym teście niewiele się zm ieniło, m im o że w kodzie dodano nową usługę. W teście jed nostko­ wym sprawdzamy jedynie, czy podczas ładow ania kontroler wysyła żądanie do /api/note. Nie obchodzi nas, czy wywołanie jest wykonywane przez usługę N oteService, czy bezpośrednio. Jest to więc bardziej test integracyjny, niezależny od podstawowej im plem entacji. Ponadto teraz serwer zwraca w odpowiedzi kod 404. W takim przypadku spodziewamy się, że ta­ blica items będzie pusta, ale teraz zmienna errorMessage powinna być aktualizowana w kontrolerze po odpowiedzi od serwera. Dodaliśmy blok expect, sprawdzający, czy tak się dzieje. Bloki afterE ach pozostają bez zmian.

Podsumowanie Rozszerzyliśmy wiadomości na tem at testowania jednostkowego kontrolerów i pokazaliśmy, jak testować kontrolery zależne od wbudowanych usług AngularJS (takich ja k $ lo c a tio n i $window). Później stworzyliśmy własne usługi i nauczyliśmy się je testować. W obu przypadkach proces po­ lega na wstrzyknięciu usługi do testu i interakcji z nią w odpowiedni sposób. Następnie przeszliśmy do testowania jednostkowego wywołań X H R przy użyciu imitacyjnej usługi $httpBackend, dostarczanej w pliku angular-m ocks.js. Pokazaliśmy, jak definiować oczekiwania i ob­ sługiwać asynchroniczne zachowania wywołań serwerowych za pom ocą funkcji f l u s h ( ) . Ponadto opisaliśmy m etody obsługi i testowania zarówno przypadków błędów, ja k i powodzeń. W następnym rozdziale poznasz filtry systemu AngularJS. Pokażemy Ci, ja k posługiwać się wbu­ dowanymi filtram i A ngularJS, oraz nauczym y Cię tworzyć nowe filtry w celu realizacji własnych zadań dotyczących formatowania.

134

|

Rozdział 7. Testowanie jednostkowe i obiekty XHR

ROZDZIAŁ 8.

Filtry

W

k i l k u p o p r z e d n i c h r o z d z i a ł a c h o p i s a l i ś m y d w a z c z t e r e c h f i l a r ó w a p lik a c j i A n g u l a r J S —

kon­

t r o le r y i u s łu g i. Z a p o m o c ą k o n t r o l e r ó w m o ż n a p o b i e r a ć d a n e d o in t e r f e j s u u ż y t k o w n i k a o r a z d e f i­ n i o w a ć p r o s t ą l o g i k ę s t y l u i p r e z e n t a c ji. N a t o m i a s t u s ł u g u ż y w a m y d o i m p l e m e n t o w a n i a w s p ó ln e j l o g i k i b iz n e s o w e j o r a z w a r s t w y w s p ó ln e j d la w s z y s t k i c h k o n t r o l e r ó w . W

t y m r o z d z i a l e o p i s u j e m y f i l t r y A n g u l a r J S . P o j e g o le k t u r z e b ę d z i e s z w ie d z ie ć , j a k i k i e d y u ż y ­

w a ć f i l t r ó w o r a z j a k s a m o d z i e l n i e u t w o r z y ć b a r d z o p r o s t y f ilt r A n g u l a r J S . N a k o ń c u z n a j d u j e s ię p o d r o z d z i a ł n a t e m a t n a j l e p s z y c h p r a k t y k i s p o s o b ó w o p t y m a l n e g o w y k o r z y s t a n i a filt r ó w .

Czym są filtry AngularJS F i l t r y A n g u l a r J S s ł u ż ą d o p r z e t w a r z a n i a d a n y c h i f o r m a t o w a n i a w a r t o ś c i w c e lu p r z e d s t a w ie n ia i c h u ż y t k o w n i k o w i . S t o s u j e się je d o w y r a ż e ń w k o d z i e H T M L l u b b e z p o ś r e d n io d o d a n y c h w k o n ­ t r o le r a c h i u s łu g a c h . W

w i ę k s z o ś c i p r z y p a d k ó w f ilt r w y k o r z y s t u j e s ię j a k o o s t a t n i p o z i o m f o r m a ­

t o w a n i a d a n y c h z m a g a z y n u , a b y p r z y s t o s o w a ć je d o p o k a z a n i a u ż y t k o w n i k o w i . T y p o w y m i p r z y ­ k ł a d a m i sy tu a c ji, w k t ó r y c h n a le ż y u ż y ć filtru , s ą p o b r a n i e z n a c z n i k a c z a s u i z m i a n a g o n a c z y t e ln y f o r m a t o r a z d o d a n i e s y m b o l u w a lu t y d o lic z b y . I n n ą w ł a ś c i w o ś c i ą f i l t r ó w A n g u l a r J S u ż y w a n y c h w w i d o k a c h je st to, że n a b ie ż ą c o d o s t a r c z a j ą d y ­ n a m i c z n y c h d a n y c h , k t ó r e n i e m u s z ą b y ć p r z e c h o w y w a n e . G d y f ilt r y s t o s u j e s ię w k o d z i e H T M L , f i l t r o w a n e w a r t o ś c i z o s t a j ą p r z e d s t a w i o n e u ż y t k o w n i k o w i , a le o r y g i n a l n e i n f o r m a c j e p o z o s t a j ą n ie z m ie n i o n e . W

t y m r o z d z ia le p o k a z u j e m y k ilk a s t a n d a r d o w y c h f ilt r ó w A n g u la r J S o r a z p r e z e n t u je m y t y p o w e

p r z y k ł a d y i c h u ż y c ia .

Stosowanie filtrów AngularJS S y s t e m A n g u la r J S z a w ie ra k ilk a w b u d o w a n y c h f ilt r ó w d o p r a c y z d a ta m i, lic z b a m i, ł a ń c u c h a m i i t a b lic a m i. T y p o w y m p r z y k ł a d e m i c h z a s t o s o w a n ia je st u ż y c ie i c h w w i d o k u j a k o o s t a t n ie g o e t a p u f o r m a t o w a n ia d a n y c h p r z e z n a c z o n y c h d o o g lą d a n ia p r z e z u ż y t k o w n ik a . S p ó j r z m y n a p r z y k ła d ilu s t r u j ą c y k i l k a c z ę s t o w y k o r z y s t y w a n y c h f i l t r ó w w a k c ji:

135

< t it l e > F ilt r y w a k c j i< / t it le > < d iv n g - c o n t r o l l e r = " F i l t e r C t r l as c t r l "> I l o ś ć w p o s t a c i li c z b y : { {c t r l. a m o u n t < /d i v>

| num ber}}

Suma w o k r e ś lo n e j w a lu c ie : { { c t r l . t o t a l C o s t | c u r re n c y }} < /d i v> Suma w INR: { { c t r l . t o t a l C o s t | c u r r e n c y : 'I N R '} } < /d i v> W y k rz y k n ię c ie n az w isk a : { {c t r l. n a m e | u p p e rc a se }} < /d i v> W ysze p tan ie n a z w isk a : { {c t r l. n a m e | lo w e rc a s e }} < /d i v> Czas r o z p o c z ę c ia : { { c t r l . s t a r t T i m e | d a te :'d d .M M .y y y y , H H :m m :ss'}} < /d i v> < / d iv > < s c r ip t s r c = " h t t p s : / / a j a x . g o o g le a p i s .c o m / a ja x / li b s / a n g u la r j s / 1 . 3 . 1 1 / a n g u la r . j s " > < / s c r ip t > < s c r i pt t y p e = "t e x t / j a v a s c r i p t "> a n g u la r . m o d u le ( 'f i l t e r s A p p ', [ ]) . c o n t r o l l e r ( 'F i l t e r C t r l ', [ f u n c t io n ( ) { th is.a m o u n t = 1024; t h i s . t o t a l C o s t = 4906; th is.n a m e = 'Shyam S e s h a d r i '; t h is . s t a r t T im e = new D a t e ( ) . g e t T im e () ; }]); < / s c r ip t > W

p r z e g lą d a r c e in t e r n e t o w e j k o d t e n p r e z e n t u j e s ię t a k , j a k p o k a z a n o n a r y s u n k u 8.1.

Ilość w postaci liczby: 1,024 Suma w określonej walucie: S4,906.00 Suma w INR: IN R4.906.00 Wykrzyknięcie nazwiska: SHYAM SESHADRI Wyszeptanie nazwiska: shyam seshadri Czas rozpoczęcia: 20.06.2015, 11:22:31 Rysunek 8.1. Wyniki działania filtrów AngularJS

136

|

Rozdział 8. Filtry

Przyjrzymy się dokładniej poszczególnym filtrom AngularJS po kolei, ale najpierw chcielibyśmy wyjaśnić, ja k się ich używa i jaka jest ich składnia. Zasadniczo w filtrach stosuje się uniksową skład­ nię polegającą na przekazaniu wyniku jednego wyrażenia do innego: {{w y r a ż e n ie | f i l t r } }

Filtr ten pobiera w artość wyrażenia (łańcuch, liczbę lub tablicę) i przekształca ją na inną postać. Na przykład użyty w powyższym przykładzie filtr cu rren cy pobiera liczbę zapisaną w zm iennej to ta lC o s t i konw ertuje ją na łańcuch z przecinkam i i kropkam i oraz sym bolem waluty1. Filtry uppercase i low ercase pobierają łańcuch i konw ertują go odpowiednio na małe bądź duże litery.

^

Pamiętaj, że zapis w rodzaju: { {c t r l. n a m e

| lo w e rc a se }}

oznacza formatowanie danych na bieżąco. To znaczy, że wartość zmiennej c tr l .name pozostaje niezmieniona, podczas gdy użytkownikowi prezentowana jest przekształ­ cona postać danych.

Filtry m ożna też łączyć za pom ocą pionowej kreski: {{w y r a ż e n ie | f i l t r l

|filtr2

}}

Powiedzmy np., że chcemy pobrać wartość zmiennej name z powyższego kontrolera, zamienić w niej wszystkie litery na małe i wyświetlić tylko pięć pierwszych z nich. Zadanie to m ożna wykonać na­ stępująco: { {c t r l. n a m e

| lo w e rc a se | lim it T o : 5 } }

Każdy filtr pobiera wartość z poprzedniego wyrażenia i przetwarza ją we właściwy sobie sposób. W tym przykładzie najpierw wszystkie litery w łańcuchu zostałyby zam ienione na m ałe, a następ­ nie zm ieniony ciąg znaków zostałby przekazany do filtru lim itT o , który zw róciłby tylko pięć pierwszych znaków, czyli napis shyam. Jak widać, do filtrów też m ożna przekazywać argumenty. W tym przypadku za pom ocą argumentu zaznaczyliśmy w filtrze lim itT o , do ilu znaków chcemy ograniczyć zwracany łańcuch.

Najczęściej używane filtry AngularJS Teraz dokładniej przyjrzymy się wszystkim wspom inanym do tej pory jedynie m im ochodem fil­ trom i kilku jeszcze niewymienianym oraz zobaczymy przykłady ich użycia. N ajpierw opisujemy filtry, a potem przedstawiamy kompletny przykład dem onstrujący ich działanie. currency Filtr currency form atuje podaną liczbę jako kwotę w określonej walucie, dodając przecinki, kropki i symbol waluty. Filtr ten przyjmuje opcjonalnie symbol waluty w drugim argumencie. Jeśli argum ent ten nie zostanie podany, zostaje użyty domyślny symbol przeglądarki.

1 System AngularJS stosuje amerykańskie konwencje zapisu liczb, czyli przecinek do rozdzielania grup cyfr i kropkę do oddzielania części dziesiętnej — przyp. tłum.

Czym są filtry AngularJS

|

137

n um b e r F il t r nu m be r p o b i e r a lic z b ę i k o n w e r t u j e ją n a c z y t e ln y ł a ń c u c h z p r z e c in k a m i. P o n a d t o filt r t e n p o b i e r a o p c j o n a l n y a r g u m e n t o k r e ś l a j ą c y lic z b ę m ie j s c p o p r z e c in k u . lo w e r c a s e B a r d z o p r o s t y f ilt r p o b ie r a j ą c y ł a ń c u c h i z a m i e n i a j ą c y w s z y s t k ie lit e r y n a m a łe . u p p e rc a se B a r d z o p r o s t y f ilt r p o b i e r a j ą c y ł a ń c u c h i z a m i e n ia j ą c y w s z y s t k ie lit e r y n a d u ż e . jso n F i l t r j s o n je st b a r d z o p o m o c n y p r z y d e b u g o w a n i u i g d y t r z e b a w y ś w ie t lić t r e ś ć o b ie k t u J S O N lu b t a b l ic y w in t e rfe jsie u ż y t k o w n i k a . P o b i e r a o b ie k t J S O N b ą d ź t a b lic ę (a n a w e t o b ie k t y t y p ó w p o d s t a w o w y c h ) i w y ś w ie t la z a w a r t o ś ć w p o s t a c i ł a ń c u c h a w in t e r f e j s ie u ż y t k o w n i k a . d a te F il t r d a t e m o ż n a d o s t o s o w y w a ć n a w ie le s p o s o b ó w . P o b ie r a o n o b ie k t d a t y a lb o z n a c z n i k c z a s u i w y ś w i e t la j e g o z a w a r t o ś ć w c z y t e ln e j p o s t a c i w in t e r f e j s ie u ż y t k o w n i k a . U ż y t k o w n i k m o ż e z d e f i n i o w a ć w ł a s n y f o r m a t d a t y i g o d z i n y l u b u ż y ć j e d n e g o z g o t o w y c h f o r m a t ó w s h o r t , medium i l o n g ( t y l k o d l a lo k a li z a c j i U S A ) . S z c z e g ó ł o w y o p i s o p c j i f o r m a t o w a n i a f ilt r u d a t e z n a j d u j e s ię w o fic j a ln e j d o k u m e n t a c j i. P o n iż e j p r z e d s t a w ia m y p r z y k ł a d o w y p r o g r a m d e m o n s t r u j ą c y s p o s ó b u ż y c ia o p is a n y c h filt r ó w w p o łą c z e n iu z ła ń c u c h a m i i lic z b a m i:

< t it l e > F ilt r y w a k c j i< / t it le >
    < li> I l o ś ć - { { c t r l. a m o u n t } } < li> I l o ś ć - dom yślna w a lu ta : { {c t r l. a m o u n t < li>

    | c u r re n c y }}

    I l o ś ć - w a lu ta fu n t s z t e r l i n g : < li> I l o ś ć - l ic z b a :

    { {c t r l. a m o u n t

    { {c t r l. a m o u n t

    | number}}

    < li> I l o ś ć - n r z czterem a m iejscam i po p r z e c in k u : < li> N azw isko bez f i l t r ó w :

    138

    |

    Rozdział 8. Filtry

    | c u r r e n c y : '& # 1 6 3 '} }

    { { c t r l. n a m e } }

    { {c t r l. a m o u n t

    | num ber:4}}

    < li> N azw isko - z f i lt r e m < li> N azw isko - z f i lt r e m

    lo w e rc a se :

    { {c t r l. n a m e

    | lo w e rc a se }}

    u pp e rcase :

    { {c t r l. n a m e

    | u p p e rc a se }}

    < li> F i l t r JSON: { { c t r l . o b j

    | jso n }}

    < li> Z n a c z n ik cz a su :

    { {c t r l. s t a r t T im e } }

    < li> Dom yślny f i l t r d a ty : < li> F i l t r d a ty medium:

    { {c t r l. s t a r t T im e

    {{c t r l. s t a r t T im e

    | d a te }}

    | d a t e : 'm e d iu m '}}

    < li> F i l t r d a ty z d e fin io w a n y p rz e z u ży tk o w n ika:

    {{c t r l. s t a r t T im e

    | d a t e : 'd d . M M . y y y y '} }

    < / u l> < s c r ip t s r c = " h t t p s : / / a j a x . g o o g le a p i s . c o m / a j a x / li b s / a n g u la r j s / 1 . 3 . 1 1 / a n g u la r . j s "> < / s c r ip t > < s c r i pt t y p e = " t e x t / j a v a s c r ip t " > an g u la r.m o d u le ( ' f i l t e r s A p p ',

    [])

    . c o n t r o l l e r ( 'F i l t e r C t r l ', [ f u n c t io n ( ) { th is.a m o u n t = 1024; th is.n a m e = 'Shyam S e s h a d r i '; t h i s . o b j = { t e s t : 'v a l u e ', num: 123}; t h is . s t a r t T im e = new D a t e ( ) . g e t T im e () ; }]); < / s c r ip t > W k o d z i e t y m w k o n t r o l e r z e z o s t a ł y z d e f i n i o w a n e c z t e r y w a r t o ś c i: • li c z b a w z m i e n n e j am ount, • ł a ń c u c h w z m i e n n e j name, • o b ie k t J S O N w z m i e n n e j o b j, • z n a c z n i k c z a s u w z m ie n n e j s t a r t T im e . K o d H T M L ( k t ó r e g o w y n i k w p r z e g l ą d a r c e p o k a z a n o n a r y s u n k u 8 .2 ) z a w i e r a n a s t ę p u j ą c e w i ą ­ z a n i a i f ilt r y : 1. Z m i e n n a am ount. 2. Z m i e n n a am o u n t z f i lt r e m c u r r e n c y w y k o r z y s t u j ą c y m d o m y ś l n y s y m b o l w a l u t y p r z e g lą d a r k i.

    Czym są filtry AngularJS

    |

    139

    • • • • • • • • • • • • •

    Ilość - 1024 Ilość - domyślna waluta: $1,024.00 Ilość - waluta funt szterling: £1,024.00 Ilość - liczba: 1,024 Ilość - nr z czterema miejscami po przecinku: 1,024.0000 Nazwisko bez filtrów: Shyam Seshadri Nazwisko • z filtrem lowercase: shyam seshadri Nazwisko * z filtrem uppercase: SHYAM SESHADRI Filtr JSON: { "test": "value", "num": 123 } Znacznik czasu: 1437399971494 Domyślny filtr daty: Jul 20, 2015 Filtr daty medium: Jul 20, 2015 3:46:11 PM Filtr daty zdefiniowany przez użytkownika: 20.07.2015

    Rysunek 8.2. Wynik działania filtrów liczbowych i łańcuchowych 3. Z m i e n n a a m o u n t z f i l t r e m c u r r e n c y z e z d e f i n i o w a n y m s y m b o l e m w a l u t y ( w t y m p r z y p a d k u je st t o f u n t s z t e r lin g ) . 4. Z m i e n n a a m o u n t z d o m y ś l n y m f il t r e m n u m b e r, k t ó r y d z i a ł a p o d o b n i e j a k f ilt r c u r r e n c y , ale b e z d o m y ś l n e j c z ę ś c i d z ie s ię t n e j i s y m b o l u w a lu t y . 5. Z m i e n n a am ou n t z f i lt r e m n u m b e r i c z t e r e m a c y f r a m i p o p r z e c in k u . 6. Z m i e n n a name. 7. Z m i e n n a name z f ilt r e m

    lo w e r c a s e , s p r a w ia j ą c y m , że n a s t r o n ie p o j a w ia się

    n a p is shyam s e s h a d r i .

    8. Z m i e n n a name z f ilt r e m

    u p p e rc a s e , s p r a w ia j ą c y m , że n a s t r o n ie p o j a w ia się

    n a p is SHYAM SE S H A D R I.

    9. Z m i e n n a o b j , k t ó r e j z a w a r t o ś ć z o s t a ł a w y d r u k o w a n a w p o s t a c i ł a ń c u c h a { " t e s t " : "n u m ":

    "v a lu e ",

    1 2 3 }.

    10. Z m i e n n a tim e s t a m p , k t ó r e j w a r t o ś ć z o s t a ł a w y k o r z y s t a n a d o s f o r m a t o w a n i a d a ty . 11. Z m i e n n a t im e s t a m p w y k o r z y s t a n a z d o m y ś l n y m f ilt r e m d a t e ( k t ó r y d r u k u j e d a tę w p o s t a c i Jan 3,

    2007 —

    p o d o b n i e j a k f o r m a t m edium ).

    12. Z m i e n n a t im e s t a m p z f ilt r e m d a t e i f o r m a t e m m edium , k t ó r y d r u k u j e d a tę i g o d z i n ę w f o r m a c ie a m e r y k a ń s k im (n p . Jan 3,

    2007

    1 2 : 0 4 : 4 5 pm).

    13. Z m i e n n a t im e s t a m p z f i lt r e m d a t e i f o r m a t e m z d e f i n i o w a n y m p r z e z u ż y t k o w n i k a . T e r a z p r z y j r z y m y się f ilt r o m p r z e z n a c z o n y m g ł ó w n ie d o p r a c y z t a b lic a m i, k t ó r e m o ż e d z ie lić i z m ie ­ n ia ć w z a le ż n o ś c i o d p o t rz e b y : lim it T o F i l t r l i m i t T o t o p r o s t y f ilt r p o b i e r a j ą c y ł a ń c u c h ( j a k w p r z y k ł a d z i e ) l u b t a b lic ę i z w r a c a j ą c y p o d z b i ó r u t w o r z o n y p r z e z p o b r a n i e e l e m e n t ó w z p o c z ą t k u a lb o k o ń c a o r y g i n a l n e g o z b io r u , w z a l e ż n o ś c i o d p r z e k a z a n e g o a r g u m e n t u . J e ż e li f il t r o w i t e m u p r z e k a z a n a z o s t a n ie t y l k o l i c z ­ b a ( n p . 3), t o z w r ó c i t y l k o t y le e l e m e n t ó w z t a b l i c y l u b z n a k ó w z ł a ń c u c h a . J e śli l i c z b a je st u j e m n a , e le m e n t y b ą d ź z n a k i z o s t a n ą p o b r a n e z k o ń c a .

    140

    |

    Rozdział 8. Filtry

    o rd e rB y J e d e n z d w ó c h b a r d z ie j s k o m p l i k o w a n y c h f i l t r ó w ( d r u g i t o f i l t e r , k t ó r e g o o p i s z n a j d u j e s ię n iż e j) . F i l t r t e n u m o ż l i w i a p o b r a n i e t a b l i c y i u p o r z ą d k o w a n i e jej w e d ł u g w y r a ż e n i a p r e d y k a t y w n e g o ( a l b o s e r i i w y r a ż e ń p r e d y k a t y w n y c h ) . P o n a d t o f ilt r t e n p o b i e r a o p c j o n a l n i e d r u g i a r g u m e n t lo g ic z n y , o k r e ś la j ą c y , c z y p o s o r t o w a n a t a b lic a m a z o s t a ć o d w r ó c o n a . N a j p r o s t s z e w y r a ż e n ie p r e d y k a t y w n e to ł a ń c u c h b ę d ą c y n a z w ą p o l a ( k l u c z k a ż d e g o o b ie k t u ) , w e d ł u g k t ó r e g o m a z o s t a ć u p o r z ą d k o w a n a t a b lic a , z o p c j o n a l n y m z n a k i e m + l u b - p r z e d n a z w ą p o la , o k r e ­ ś la j ą c y m i , c z y e l e m e n t y m a j ą z o s t a ć p o s o r t o w a n e r o s n ą c o , c z y m a le j ą c o . W y r a ż e n i u m o ż n a te ż p r z e k a z a ć f u n k c j ę i w ó w c z a s o u p o r z ą d k o w a n i u e l e m e n t ó w d e c y d u j e w a r t o ś ć z w r o t n a tej fu n k c ji ( p r z y u ż y c iu o p e ra to r ó w p o r ó w n y w a n ia

    <, > i =). P o n a d t o w y r a ż e n ie p r e d y k a t y w n e

    m o ż e b y ć ta b licą , k tó re j k a ż d y e le m e n t je st ł a ń c u c h e m b ą d ź fu n k c ją . W ó w c z a s s y s t e m A n g u l a r J S p o s o r t u j e z b i ó r d a n y c h w e d ł u g p i e r w s z e g o e le m e n t u tej t a b lic y , a je ś li n a p o t k a d w a t a k ie s a m e e le m e n t y , d o u s t a l e n ia i c h k o l e j n o ś c i w y k o r z y s t a d r u g i e le m e n t . filt e r J e d n y m z n a j b a r d z ie j e la s t y c z n y c h f i l t r ó w w s y s t e m ie A n g u l a r J S je st f i l t e r . N a z w a m o ż e b y ć m y lą c a , ale f ilt r t e n s ł u ż y d o f i l t r o w a n i a t a b lic n a p o d s t a w ie p r e d y k a t ó w i f u n k c j i o r a z w y b i e ­ r a n ia , k t ó r e e le m e n t y t a b l ic y z o s t a w ić . F i l t r t e n je st n a j c z ę ś c ie j w y k o r z y s t y w a n y w p o ł ą c z e n i u z d y r e k t y w ą n g - r e p e a t d o d y n a m i c z n e g o f i l t r o w a n i a t a b lic . W y r a ż e n i e f ilt r u j ą c e m o ż e b y ć je d n e go z n a stę p u ją c y c h typ ó w : s t r in g Je śli z o s t a n ie p r z e k a z a n e w y r a ż e n ie ł a ń c u c h o w e , s y s t e m A n g u l a r J S b ę d z ie s z u k a ł ł a ń c u c h a w k l u c z a c h k a ż d e g o o b i e k t u z n a j d u j ą c e g o s ię w t a b l ic y (tz n . w a r t o ś ć ł a ń c u c h o w a b ę d z ie p o r ó w n y w a n a z w a r t o ś c i ą k a ż d e g o k l u c z a w k a ż d y m o b ie k c ie ) . J e ż e li w k t ó r y m k o l w i e k k l u c z u w a r t o ś ć ta z o s t a n ie z n a le z io n a , p o s ia d a j ą c y j ą o b ie k t p r z e j d z ie p r z e z f ilt r i z o s t a n ie w y ś w i e t l o n y w r a m a c h d y r e k t y w y n g - r e p e a t . E w e n t u a l n ie p r z e d ł a ń c u c h e m m o ż n a d o d a ć o p e r a t o r !, o z n a c z a j ą c y n e g a c j ę d o p a s o w a n ia . o b je c t O b i e k t r e p r e z e n t u j ą c y w z o r z e c , k t ó r y z o s t a n ie p o r ó w n a n y z k a ż d y m o b ie k t e m z n a j d u j ą ­ c y m s ię w f i lt r o w a n e j t a b lic y . N a p r z y k ł a d w y r a ż e n i e o b i e k t o w e { s i z e :

    "M "} p a su je d o

    w s z y s t k i c h o b i e k t ó w z a w i e r a j ą c y c h k l u c z o n a z w ie s i z e i w a r t o ś c i z a w ie ra ją c e j lit e rę "M " ( d o p a s o w a n i e n i e m u s i b y ć d o k ła d n e ) . fu n c t io n N a j b a r d z i e j e la s t y c z n a i w s z e c h s t r o n n a z w s z y s t k i c h o p c ji, u m o ż l i w i a j ą c a p r z e k a z a n i e f u n k c j i im p le m e n t u j ą c e j d o w o l n y filtr. F u n k c j a t a je st w y w o ł y w a n a d la k a ż d e g o e le m e n t u t a b l ic y i jej w a r t o ś ć z w r o t n a je st w y k o r z y s t y w a n a d o o k r e ś l a n ia , c z y d a n y e le m e n t p o w i ­ n i e n z o s t a ć d o d a n y d o w y n i k u . K a ż d y e le m e n t , d la k t ó r e g o f u n k c j a z w r ó c i f a ł s z y w ą w a r ­ to ść , z o s t a j e o d r z u c o n y . Z o b a c z m y , j a k d z ia ła j ą te filt r y , n a k o n k r e t n y m p r z y k ł a d z i e :

    < h tm l> < t it l e > F ilt r y w a k c j i< / t it le >

    Czym są filtry AngularJS

    |

    141

    < d iv n g - c o n t r o l l e r = " F i l t e r C t r l

    as c t r l ">

    F i l t r z o p c ją s t r i n g < /b u tto n > < b u tto n n g - c l i c k = " c t r l . c u r r e n t F i l t e r = 'o b j e c t '" > F i l t r z o p c ją o b je ct < /b u tto n > < b u tto n n g - c l i c k = " c t r l . c u r r e n t F i l t e r = 'f u n c t i o n '" > F i l t r z o p c ją fu n c t io n < /b u tto n > F i l t r t e k s tu < in p u t t y p e = "t e x t " n g - m o d e l = " c t r l . f i l t e r O p t i o n s [ 's t r i n g '] " > Pokaż t y lk o z ro b io n e < in p u t ty p e = "c h e c k b o x " n g - m o d e l = " c t r l . f i l t e r O p t i o n s [ ' o b j e c t ' ] .do ne "> < u l> < l i n g -r e p e a t = "n o te in c t r l . n o t e s | f i l t e r : c t r l. f ilt e r O p t i o n s [ c t r l. c u r r e n t F ilt e r ] o r d e r B y : c t r l. s o r t O r d e r | l i m it T o :5 "> { {n o te . l a b e l } } - { { n o t e . t y p e } } - { {n o t e .d o n e }} < / li> < / u l>

    |

    < / d iv > < s c r ip t s r c = " h t t p s : / / a j a x . g o o g le a p i s .c o m / a ja x / li b s / a n g u la r j s / 1 . 3 . 1 1 / a n g u la r . j s " > < / s c r ip t > < s c r i pt t y p e = "t e x t / j a v a s c r i p t "> a n g u la r . m o d u le ( 'f i l t e r s A p p ', [ ]) . c o n t r o l l e r ( 'F i l t e r C t r l ', s.n o te s = la b e l la b e l la b e l la b e l la b e l la b e l la b e l la b e l la b e l

    'FC 'F T 'F F 'S C 'S T 'S F 'TC 'TT 'T F

    Todo' Todo' Todo' Todo' Todo' Todo' Todo' Todo' Todo'

    [ f u n c t io n ( )

    type: type: type: type: type: type: type: type: type:

    ]; t h i s . s o r t O r d e r = [ '+ t y p e ',

    {

    'c h o r e ', done: f a l s e } 't a s k ', done: f a l s e } , 'f u n ', done: t r u e } , 'c h o r e ', done: t r u e } , 't a s k ', done: t r u e } , 'f u n ', done: t r u e } , 'c h o r e ', done: f a l s e } 't a s k ', done: f a l s e } , 'f u n ', done: f a l s e } ' - l a b e l '] ;

    t h is . f ilt e r O p t io n s = { "s t r in g ": '', "o b j e c t " : {done: f a l s e , la b e l: 'C '} , " f u n c t io n " : f u n c t io n ( n o t e ) { r e t u r n n o te .ty p e === 't a s k ' && n o te .do n e === f a l s e ; } };

    142

    I

    Rozdział 8. Filtry

    t h i s . c u r r e n t F i l t e r = ' s t r i n g '; }]); < / s c r ip t > W

    p r z y k ł a d z i e t y m u ż y w a m y w s z y s t k i c h f i l t r ó w z w i ą z a n y c h z t a b lic a m i. U t w o r z y l i ś m y t a b lic ę

    n o t a t e k ( n o t e s ) z t r z e m a k l u c z a m i ( l a b e l , t y p e i d o n e ). Z d e f i n i o w a l i ś m y t e ż t a b lic ę p r e d y k a t ó w s o r t o w a n i a , s o r t O r d e r , a b y n a j p i e r w p o s o r t o w a ć e l e m e n t y w e d ł u g t y p u , a j e ś l i s ą j e d n a k o w e , to w o d w r ó c o n e j k o l e j n o ś c i, w e d ł u g e t y k ie t y ( l a b e l ). W

    k o d z i e H T M L s o r t u j e m y e le m e n t y w e d ł u g s o r t O r d e r i o g r a n ic z a m y w y n i k i d o p ię c iu e le m e n t ó w .

    W c z e ś n i e j j e d n a k f ilt r u j e m y t a b lic ę w e d ł u g f i l t e r O p t i o n s . D o m y ś l n i e u ż y w a m y f ilt r u s t r i n g , k t ó r y je st z w i ą z a n y z p o l e m t e k s t o w y m . Je śli w p o l u t y m c o ś w p is z e m y , z o s t a n ie to p o r ó w n a n e z w s z y s t ­ k i m i p o l a m i w k a ż d e j n o t a t c e i w y ś w ie t lo n e z o s t a n ą t y lk o p a s u j ą c e e le m e n t y . K l i k n i ę c i e p r z y c i s k ó w s p o w o d u j e p r z e łą c z e n ie t r y b u f i l t r o w a n i a ze s t r i n g n a o b j e c t l u b f u n c t i o n . F i l t r o b j e c t p o k a z u j e w s z y s t k i e n o t a t k i, k t ó r e n i e s ą s k o ń c z o n e (d o n e ) i k t ó r e m a j ą w e t y k ie c ie l i ­ te rę C. P o l e w y b o r u s ł u ż y d o w ł ą c z a n i a i w y ł ą c z a n i a f ilt r a d o ne , a b y m o ż n a b y ł o w y ś w ie t lić t y lk o n o t a t k i s k o ń c z o n e a lb o t y lk o n i e s k o ń c z o n e . F i l t r f u n c t i o n p o k a z u j e t y lk o te n o t a t k i, k t ó r e s ą z a d a n i a m i i n ie s ą s k o ń c z o n e . W

    p r a w d z iw e j a p lik a c j i m o g l i b y ś m y w ią z a ć p o l a w y b o r u t y p u s e l e c t i t e k s t o w e z r ó ż n y m i p o l a m i

    o b ie k t u ( p r z y u ż y c i u d y r e k t y w y n g - m o d e l) o r a z u ż y w a ć o b ie k t u d o d y n a m i c z n e g o f ilt r o w a n ia listy. F il t r f u n k c y j n y m o ż n a b y b y ł o r o z b u d o w a ć w o d n i e s i e n i u d o l o g i k i b iz n e s o w e j . P i ę k n o f ilt r u f i l t e r p o l e g a n a t y m , że je st d y n a m i c z n y w b e z p o ś r e d n i m p o ł ą c z e n iu z k o d e m H T M L , d z ię k i c z e m u g d y z m i e n i się c o ś w p o d s t a w o w y m m o d e lu , n a s t ę p u j e a u t o m a t y c z n e p r z e f ilt r o w a n ie całej listy.

    Używanie filtrów w kontrolerach i usługach F i l t r y d o s k o n a l e s p r a w d z a j ą się w k o d z i e H T M L i in t e rfe jsie u ż y t k o w n i k a , ale c z a s a m i r ó ż n e p r z e ­ k s z t a łc e n ia t r z e b a w y k o n y w a ć t a k ż e w k o n t r o l e r a c h i u s łu g a c h . N a s z c z ę ś c ie d z ię k i w s t r z y k i w a n i u z a le ż n o ś c i s y s t e m A n g u l a r J S p o z w a l a n a s t o s o w a n i e f ilt r ó w , g d z ie s ię t y lk o c h c e . M ó w i ą c k r ó t k o , f ilt r ó w m o ż n a u ż y w a ć b e z p o ś r e d n i o w k o d z i e J a v a S c r ip t , b e z o d w o ł y w a n i a się w j a k i k o l w i e k s p o s ó b d o s t r u k t u r y D O M c z y in t e r f e j s u u ż y t k o w n i k a . K a ż d y filtr, z a r ó w n o w b u d o w a n y , j a k i n a p i s a n y p r z e z u ż y t k o w n i k a , m o ż n a w s t r z y k n ą ć d o u s ł u g i i k o n t r o l e r a p r z e z d o d a n i e s ł o w a F i l t e r n a k o ń c u j e g o n a z w y filt ru . N a p r z y k ł a d je ś li w k o n t r o l e r z e p o t r z e b n y je st f ilt r c u r r e n c y , m o ż e m y g o w s t r z y k n ą ć d o t e g o k o n t r o l e r a w n a s t ę p u j ą c y s p o s ó b : a n g u la r .m o d u le ('m y M o d u le ',

    [ ])

    . c o n t r o ll e r ( 'M y C t r l ', [ 'c u r r e n c y F i l t e r ', f u n c t io n ( c u r r e n c y F ilt e r ) { }]); A n a l o g i c z n i e f il t r n u m b e r z a m i e n i a s ię w n u m b e r F i l t e r , a f i l t e r p r z y j m u j e d z i w a c z n ą p o s t a ć f i l t e r F i l t e r . D o ł ą c z e n i e s ł o w a F i l t e r p o z w a l a n a w s t r z y k n ię c ie d o w o l n e g o f il t r u d o k o n t r o l e r a l u b u s łu g i.

    Czym są filtry AngularJS

    |

    143

    W kodzie H TM L do przekazywania filtrom danych do przetworzenia służy pionowa kreska i ewentu­ alne argumenty. N atom iast w kontrolerach i usługach używa się funkcji. Jej pierwszy argument jest wartością, na której m a pracować filtr — może to być łańcuch, liczba bądź tablica. W szystkie dodatkowe param etry są argum entam i, o których była mowa wcześniej. Jeśli więc chcemy przefiltrować tablicę s e l f . n o t e s za pom ocą filtru f i l t e r F i l t e r przy użyciu tylko łańcucha, m ożem y wyrazić to w następujący sposób: s e lf . f ilt e r e d A r r a y = f ilt e r F ilt e r ( s e lf . n o t e s ,

    'c h ') ;

    W przykładzie tym należy zwrócić uwagę na trzy kwestie: • Pierwszy argum ent filtru jest wartością do przetworzenia. • Kolejne argumenty to param etry potrzebne filtrowi do działania (niektóre opcjonalne), które należy wpisać w kolejności określonej w dokum entacji. • W artość zwrotna filtru jest wynikiem, którego potrzebujemy.

    Tworzenie filtrów AngularJS W poprzedniej części rozdziału pokazaliśmy, jak używać niektórych wbudowanych filtrów systemu AngularJS, ale czasami to nie wystarczy. Filtr d a t e jest przydatny, jednak czasami wolelibyśmy coś sform atow ać według własnych potrzeb. Może być potrzebny filtr dla lokalizacji. W tym podroz­ dziale dowiesz się właśnie, jak tworzyć własne filtry. W ram ach przykładu napiszem y bardzo prosty filtr o nazwie t im e A g o . W produkcji wykorzystali­ byśm y rozwiązanie typu M om entJS (h ttp ://m o m en tjs.co m /), ale tu napiszem y własne bardzo pro­ ste narzędzie. Filtr, o którym mowa, m a za zadanie pobierać znacznik czasu i wyświetlać w inter­ fejsie użytkownika informacje typu „sekund temu”, „minut temu”, „dni temu” oraz „miesięcy temu”. Będziemy pokazywać tylko te komunikaty, bez jakichkolwiek liczb. Zatem bez względu na to, czy coś wydarzyło się 5, czy 15 sekund tem u, na stronie pojawi się napis „sekund tem u”. Jak to zrobić? < t it le > W ła s n e f i l t r y u ż ytk o w n ika w a k c j i < / t i t l e > < d iv n g - c o n t r o l l e r = " F i l t e r C t r l as c t r l "> P o czą te k (z n a c z n ik c z a s u ) : { { c t r l . s t a r t T i m e } } < /d i v> P o czą te k (D ate T im e ): { { c t r l . s t a r t T i m e | d a te :'d d .M M .y y y y , H H :m m :ss'}} < /d i v> P o czą te k (w ła sn y f i l t r ) : { { c t r l . s t a r t T i m e | tim eA go}} < /d i v> someTimeAgo : {{c trl.so m e T im e A g o < /d i v>

    144

    |

    Rozdział 8. Filtry

    | d a t e : 'd . M . y y , H : m : s '} }

    someTimeAgo (w ła sn y f i l t r ) : < / d iv > < / d iv >

    {{c trl.so m e T im e A g o

    | tim eA go}}

    < s c r i pt s r c = " h t t p s : / / a j a x . g o o g le a p i s . c o m / a j a x / li b s / a n g u la r j s / 1 . 3 . 1 1 / a n g u la r . j s "> < / s c r ip t > < s c r i pt t y p e = " t e x t / j a v a s c r ip t " > a n g u la r . m o d u le ( ' f i l t e r s A p p ', []) . c o n t r o l l e r ( 'F i l t e r C t r l ', [ f u n c t io n ( ) { t h is . s t a r t T im e = new D a t e ( ) . g e t T im e () ; th is.som e T im e A go = new D a t e ().g e tT im e () (1000 * 60 * 60 * 4 );

    -

    }]) . f i l t e r ( 't i m e A g o ', [ f u n c t io n ( ) { v a r ONE_MINUTE = 1000 * 60; v a r ONE_HOUR = ONE_MINUTE * 60; v a r ONE_DAY = ONE_HOUR * 24; v a r ONE_MONTH = ONE_DAY * 30; re tu rn fu n c t io n (ts ) { v a r c u rre n tT im e = new D a t e ( ) . g e t T im e () ; v a r d i f f = c u rre n tT im e - t s ; i f ( d i f f < ONE_MINUTE) { r e t u r n 'se k u n d tem u '; } e ls e i f ( d i f f < ONE_HOUR) { r e t u r n 'm in u t tem u '; } e ls e i f ( d i f f < ONE_DAY) { r e t u r n 'g o d z in tem u '; } e ls e i f ( d i f f < ONE_MONTH) { r e t u r n 'd n i tem u '; } e ls e { r e t u r n 'm ie s ię c y tem u '; } }; }]); < / s c r ip t >
    W

    p r z y k ł a d z i e t y m z d e f i n i o w a l i ś m y w ł a s n y f ilt r o n a z w ie t im e A g o . F i l t r y d e f in iu j e s ię b a r d z o p o ­

    d o b n i e j a k k o n t r o l e r y i u s ł u g i ; m o ż n a d o n i c h w s t r z y k i w a ć w s z e lk ie p o t r z e b n e u s łu g i. K a ż d y f ilt r z w r a c a f u n k c j ę , k t ó r a je st w y w o ł y w a n a z a k a ż d y m r a z e m , g d y f ilt r t e n je st u ż y w a n y . F u n k c j a ta je st w y w o ł y w a n a z w a r t o ś c ią , d o k t ó r e j f ilt r z o s t a ł z a s t o s o w a n y . W

    n aszym p rzyp a d ku

    je st t o z n a c z n i k c z a s u w p o s t a c i lic z b y . F u n k c j a f ilt r u j ą c a p o b i e r a p r z e k a z a n ą jej w a r t o ś ć i w y k o ­ n u j e n a n ie j s w o j e d z ia ła n ia . P r z y k ł a d o w a f u n k c j a t y l k o p o r ó w n u j e p r z e k a z a n y jej z n a c z n i k c z a s u z b i e ż ą c y m c z a s e m o r a z z w r a c a o d p o w i e d n i ł a ń c u c h n a p o d s t a w ie o b l ic z o n e j r ó ż n ic y . Je śli c h c e m y u m o ż l i w i ć p r z e k a z y w a n ie a r g u m e n t ó w o p c j o n a l n y c h , j a k w f ilt r a c h c u r r e n c y i num ber, to m u s i m y d o d a ć je j a k o d o d a t k o w e p a r a m e t r y z w r a c a n e j p r z e z n a s fu n k c j i. P o w ie d z m y , ż e c h c e m y p o b i e r a ć a r g u m e n t l o g i c z n y ( n p . i g n o r e S e c o n d s ) n a k a z u j ą c y p o k a z y w a n i e t y lk o m in u t . W

    t a k im

    r a z ie w a r t o ś ć z w r o t n a p o w i n n a z o s t a ć z m i e n i o n a w n a s t ę p u j ą c y s p o s ó b : re tu rn fu n c t io n ( t s ,

    ign o re S e c o n d s)

    {

    Tworzenie filtrów AngularJS

    |

    145

    W a r t o ś ć d l a t e g o a r g u m e n t u w k o d z i e H T M L p r z e k a z y w a ł o b y s ię tak: { {c t r l. s t a r t T im e

    | t im e A g o : t r u e } }

    J e śli p o t r z e b n y c h j e s t w ie le a r g u m e n t ó w , n a l e ż y je r o z d z i e l i ć p r z e c i n k a m i i p r z e k a z a ć w k o d z i e H T M L w t a k ie j s a m e j k o l e j n o ś c i: r e t u r n f u n c t io n ( t s , a r g l , a rg 2 , a rg 3 )

    {

    i: { {c t r l. s t a r t T im e

    | t im e A g o : a r g 1 : a r g 2 : a r g 3 } }

    M e t o d y t e s t o w a n i a t a k i c h f i l t r ó w z o s t a ł y o p i s a n e w r o z d z ia l e 9.

    Co trzeba zapamiętać o filtrach P o k a z a l i ś m y , j a k p o s ł u g i w a ć s ię s t a n d a r d o w y m i f i l t r a m i s y s t e m u A n g u l a r J S o r a z j a k m o d y f i k o ­ w a ć i c h d z i a ł a n ie z a p o m o c ą a r g u m e n t ó w . P ó ź n ie j o p i s a l i ś m y m e t o d y w y k o r z y s t a n i a t y c h f il t r ó w w k o n t r o l e r a c h i u s łu g a c h , a n a s t ę p n i e p r z e d s t a w i l i ś m y t e c h n i k i p i s a n i a w ł a s n y c h f ilt r ó w . W

    tym

    p o d r o z d z ia le p r e z e n t u j e m y z b i ó r n a j le p s z y c h p r a k t y k i r z e c z y , o k t ó r y c h w a r t o p a m ię t a ć p r z y p r a c y z f il t r a m i w s y s t e m ie A n g u la r J S :

    F iltry w idoków są wykonywane w każdym cyklu obliczeniow ym P r z e d e w s z y s t k i m n a l e ż y w ie d z ie ć , że j e ś li f i lt r y A n g u l a r J S s ą u ż y w a n e b e z p o ś r e d n i o w w i d o ­ k u ( c o n ie je st r z a d k o ś c i ą ) , s ą w y k o n y w a n e w k a ż d y m c y k l u o b l i c z e n i o w y m ( s z c z e g ó ł o w o n a t e n t e m a t p i s z e m y w r o z d z i a l e 13., w p u n k c i e „ C y k l o b l i c z e n i o w y ”). Z a t e m t r z e b a m ie ć ś w i a ­ d o m o ś ć , że w m ia r ę j a k r o ś n i e i l o ś ć d a n y c h d o p r z e t w o r z e n ia , p o t r z e b a c o r a z w ię c e j c z a s u n a w y k o n y w a n i e o b lic z e ń , z w ł a s z c z a g d y w in t e r f e j s ie u ż y t k o w n i k a je st u ż y w a n y c h d u ż o filt r ó w .

    F iltry pow inny być szybkie ja k błyskaw ica O d n o s z ą c s ię d o p o p r z e d n i e g o p u n k t u —

    j e ś li p i s z e m y w ł a s n y filt r , p o w i n n i ś m y z r o b i ć to

    t a k , a b y d z i a ł a ł o n b a r d z o s z y b k o . N a j le p ie j , b y c z a s w y k o n y w a n i a n a s z y c h f u n k c j i f i l t r u j ą ­ c y c h m i e r z y ł o s ię w m i l i s e k u n d a c h . D l a t e g o w f i l t r a c h p o w i n n o s ię u n i k a ć t a k i c h c z y n n o ś c i j a k m a n ip u la c ja s t r u k t u r ą D O M , w y k o n y w a n ie w y w o ła ń a s y n c h r o n ic z n y c h i in n y c h p o w o l­ n y c h d z ia ła ń .

    Ze względu n a w ydajność p referu j u m iejscow ienie filtró w w usługach i k o n tro lera ch J e ż e li w y k o r z y s t u j e s z d u ż e i s k o m p l i k o w a n e t a b l ic e l u b i n n e s t r u k t u r y d a n y c h , a le c h c e s z u ż y w a ć f i l t r ó w ze w z g l ę d u n a w y g o d ę i i c h m o d u l a r n ą b u d o w ę , t o m o ż e s z s p r ó b o w a ć s t o s o ­ w a ć f i lt r y b e z p o ś r e d n i o w k o n t r o l e r z e b ą d ź u s łu d z e . W s t r z y k n i j f ilt r d o k o n t r o l e r a l u b u s ł u g i i w y w o ł a j f u n k c j ę f ilt r u j ą c ą w r a z ie p o t r z e b y ( j a k p o k a z a l i ś m y w p u n k c i e „ U ż y w a n i e f i l t r ó w w k o n t r o l e r a c h i u s ł u g a c h ”). D z i ę k i t e m u in t e r f e j s u ż y t k o w n i k a b ę d z i e s z y b c ie j r e a g o w a ł . M e t o d a t a je st z a le c a n a z a m i a s t b e z p o ś r e d n i e g o s t o s o w a n i a f i l t r ó w d o d u ż y c h t a b lic . P o n a d t o m o ż n a u n i e m o ż l i w i ć w y k o n a n i e f ilt r u , je śli n i c się n ie z m ie n iło , c o p o z w a la z a o s z c z ę d z ić t r o c h ę c za su p ra c y p ro ce so ra.

    146

    |

    Rozdział 8. Filtry

    Podsumowanie W

    t y m r o z d z i a l e o p i s a l i ś m y f ilt r y A n g u l a r J S , k t ó r e s ą d o s k o n a ł y m n a r z ę d z i e m d o f o r m a t o w a n i a

    i k o n w e r t o w a n ia d a n y c h i w a r t o ś c i z j e d n e g o f o r m a t u n a in n y . P ó ź n ie j p r z e d s t a w iliś m y o g ó ln e m e t o d y u ż y w a n i a i ł ą c z e n ia f ilt r ó w , a b y n a s t ę p n i e p r z e j ś ć d o o p i s u w b u d o w a n y c h f i l t r ó w s y s t e m u A n g u la r J S i s p o s o b ó w ic h d o s t o s o w y w a n ia d o in d y w id u a ln y c h p o trz e b . P o t e m u t w o r z y liś m y w ła ­ s n y f ilt r o n a z w i e t im e A g o , w y ś w ie t la j ą c y , ile c z a s u u p ł y n ę ł o o d o k r e ś l o n e g o m o m e n t u . N a z a k o ń ­ c z e n ie o p i s a l i ś m y k i l k a n a j l e p s z y c h p r a k t y k p r o g r a m i s t y c z n y c h i k w e s t ii, k t ó r e n a l e ż y u w z g l ę d n i ć p o d c z a s p r a c y z w ł a s n y m i f ilt r a m i. W

    n a s t ę p n y m r o z d z ia l e p o k a z u j e m y , j a k t e s t o w a ć w ł a s n o r ę c z n i e n a p i s a n e filtry .

    Podsumowanie

    |

    147

    148

    I

    Rozdział 8. Filtry

    _____________________________ ROZDZIAŁ 9.

    Testowanie jednostkowe filtrów

    W rozdziale 8. opisaliśmy istniejący filtry systemu AngularJS oraz pokazaliśmy, ja k tworzyć własne takie konstrukcje. Filtry są doskonałym sposobem na wydzielenie ogólnej logiki form atow ania i konw ersji do osobnych kom ponentów w ielokrotnego użytku. W jeszcze w cześniejszych ro z­ działach pokazaliśm y też, ja k tworzyć kontrolery i usługi oraz ja k je testować. W tym rozdziale będziem y pracować z utworzonym w poprzednim rozdziale filtrem timeAgo. N ajpierw rozszerzymy go o opcjonalne param etry, a później wykonamy na nim dokładne testy jednostkow e. Na końcu rozdziału będziemy m ieć testy jednostkow e Jasm ine dla wszystkich m oż­ liwych przypadków użycia naszego filtru.

    Testowanie filtru Jako podstawę w tym rozdziale wykorzystamy filtr timeAgo, opisany w podrozdziale „Tworzenie filtrów A ngularJS”. Dodam y do niego opcjonalny argum ent pozwalający określić, czy m a być wy­ świetlany napis sekund temu, czy od razu powinien się wyświetlać napis minut temu: / / Plik: r09/timeAgoFilter.js angul a r . m o d u l e ( 'f i l t e r s A p p ', []) . f i l t e r ( 't i m e A g o ', [ f u n c t io n ( ) { v a r ONE_MINUTE = 1000 * 60; v a r ONE_HOUR = ONE_MINUTE * 60; v a r ONE_DAY = ONE_HOUR * 24; v a r ONE_MONTH = ONE_DAY * 30; r e t u r n f u n c t i o n ( t s , optShow SecondsM essage) if

    (optShow SecondsM essage !== f a l s e ) optShow SecondsM essage = tru e ;

    {

    {

    } v a r c u rre n tT im e = new D a t e ( ) . g e t T im e () ; v a r d i f f = cu rre n tT im e - t s ; i f ( d i f f < ONE_MINUTE && optShow SecondsM essage) r e t u r n 'se k u n d tem u '; } e ls e i f ( d i f f < ONE_HOUR) { r e t u r n 'm in u t tem u '; } e ls e i f

    ( d i f f < ONE_DAY)

    {

    {

    149

    retu rn } e ls e i f retu rn } e ls e { retu rn

    'g o d z in tem u '; ( d i f f < ONE_MONTH) { 'd n i tem u '; 'm ie s ię c y te m u ';

    } }; }]);

    Z m iany w filtrze timeAgo są bardzo nieznaczne. Dodaliśm y do niego opcję konfiguracyjną po­ zwalającą określić, czy na początku m a być wyświetlany napis sekund temu, czy od razu minut temu. Dom yślnie napis sekund temu jest wyświetlany. Filtru tego m ożna użyć np. tak: {{ m y C t r l. t s

    | tim eAgo }}

    Jeśli zechcem y pom inąć pierwszy napis, m ożem y ustawić opcjonalny argum ent na wartość fa ls e : {{ m y C t r l. t s

    | t im e A g o : fa ls e }}

    Testowanie filtru timeAgo W rozdziale 8. pokazaliśmy, jak się tworzy filtry i ja k się ich używa w kodzie H TM L, kontrolerach oraz usługach. W testach jednostkow ych filtrów używa się podobnie ja k w kontrolerach (tzn. filtr wstrzykuje się do testów, a następnie wykonuje się je bezpośrednio jako funkcje). Później sprawdza się wartości zwrotne w celu weryfikacji, czy filtr działa prawidłowo i zgodnie z oczekiwaniami: // Plik: r09/timeAgoFilterSpec.js d e s c r i b e ( 'F i l t r t im e A g o ', f u n c t io n ( ) b e f o r e E a c h ( m o d u le ( 'f i lt e r s A p p ') ) ;

    {

    var f ilt e r ; b e f o r e E a c h ( in j e c t ( f u n c t io n ( t im e A g o F ilt e r ) f i l t e r = t im e A g o F ilt e r ;

    {

    })); i t ( 'P o w in ie n z w ró c ić odpowiedź o dn o szą cą s i ę do z n a c z n ik a c z a s u . ',

    f u n c t io n ( )

    {

    // Obecnośćfunkcji Date().getTime() trochę utrudnia jednostkowe testowanie tego filtru w sposób deterministyczny. // Najlepiej byłoby wstrzyknąć dostawcę dateProvider do filtru timeAgo, ale staramy się napisać ja k najprostszy kod. // Dlatego zakładamy, że nasze testy będą na tyle szybkie, że czas ich wykonywania nie przekroczy milisekund. v a r c u rre n tT im e = new D a t e ( ) . g e t T im e () ; cu rre n tT im e -= 10000; e x p e c t ( f i lt e r ( c u r r e n t T i m e ) ) . t o E q u a l( ' sekund te m u '); v a r few M inutesA go = cu rre n tT im e - 1000 * 60; e x p e c t ( f ilt e r ( f e w M in u t e s A g o ) ) . t o E q u a l( 'm in u t tem u') ; v a r few HoursAgo = c u rre n tT im e - 1000 * 60 * 68; e x p e c t ( f ilt e r ( f e w H o u r s A g o ) ) . t o E q u a l( ' go d zi n tem u') ; v a r few DaysAgo = cu rre n tT im e - 1000 * 60 * 60 * 26; e x p e c t ( f il t e r ( f e w D a y s A g o ) ) . t o E q u a l( 'd n i t e m u '); v a r fewMonthsAgo = c u rre n tT im e - 1000 * 60 * 60 * 24 * 32; e x p e c t ( f ilt e r ( f e w M o n t h s A g o ) ) . t o E q u a l( 'm i e s i ę c y te m u '); }); });

    150

    |

    Rozdział 9. Testowanie jednostkowe filtrów

    W teście tym nie m a nic nowego ani niezwykłego. W bloku b e f o r e E a c h tworzymy egzemplarz m odułu i w strzykujem y nasz filtr do testu (przy użyciu t i m e A g o F i l t e r — p am iętaj, że system AngularJS autom atycznie dodaje słowo F i l t e r za nazwą filtru). Dalej znajduje się im plem entacja testu jednostkowego, w którym bezpośrednio wywołujemy filtr, przekazując mu wartość do przefiltrowania. W tym przypadku tworzymy bieżący znacznik czasu i m odyfikujem y go tak, aby zo­ stał wykonany każdy warunek i f . W teście tym oczywiście pom inęliśm y argum ent opcjonalny. Co trzeba zrobić, żeby to naprawić? / / Plik: r09/timeAgoFilterOptionalArgumentSpec.js d e s c r i b e ( 'F i l t r t im e A g o . ', f u n c t io n ( ) b e fo re E a c h (m o d u le (' f i l t e r s A p p ') ) ;

    {

    var f ilt e r ; b e f o r e E a c h ( in j e c t ( f u n c t io n ( t im e A g o F ilt e r ) f i l t e r = t im e A g o F ilt e r ; }));

    {

    it ( 'P o w i n i e n z w ró c ić odpowiedź o dn o szą cą s i ę do z n a c z n ik a c z a s u . ',

    f u n c t io n ( )

    {

    // Obecnośćfunkcji Date().getTime() trochę utrudnia jednostkowe testowanie tego filtru w sposób deterministyczny. // Najlepiej byłoby wstrzyknąć dostawcę dateProvider do filtru timeAgo, ale staramy się napisać ja k najprostszy kod. // Dlatego zakładamy, że nasze testy będą na tyle szybkie, że czas ich wykonywania nie przekroczy milisekund. v a r cu rre n tT im e = new D a t e ( ) . g e t T im e () ; c u rre n tT im e -= 10000; e x p e c t ( f ilt e r ( c u r r e n t T im e , f a l s e ) ) . t o E q u a l ( 'm i n u t t e m u '); v a r few M inutesA go = c u rre n tT im e - 1000 * 60; e x p e c t ( f ilt e r ( f e w M in u t e s A g o , f a l s e ) ) . t o E q u a l ( 'm i n u t t e m u '); v a r few HoursAgo = c u rre n tT im e - 1000 * 60 * 68; e x p e c t ( f ilt e r ( f e w H o u r s A g o , f a l s e ) ) . t o E q u a l ( 'g o d z i n te m u '); v a r few DaysAgo = c u rre n tT im e - 1000 * 60 * 60 * 26; e x p e c t ( f ilt e r ( f e w D a y s A g o , f a l s e ) ) . t o E q u a l ( 'd n i t e m u '); v a r fewMonthsAgo = cu rre n tT im e - 1000 * 60 * 60 * 24 * 32; e x p e c t ( filt e r ( f e w M o n t h s A g o , f a l s e ) ) . t o E q u a l ( 'm i e s i ę c y te m u '); }); });

    Argum enty opcjonalne m ożna przekazać do filtru jako dodatkowe param etry funkcji filtrującej. W tym przypadku przekazaliśm y w artość f a l s e , w yłączającą napis dotyczący sekund. Nasz test zaczyna testow anie od m inut, więc warunki c u r r e n t T im e i f e w M in u t e s A g o zw racają napis m in u t te m u ,

    a nie napisy s e k u n d tem u i m in u t te m u , ja k to było w poprzednim przypadku.

    Podsumowanie Testow an ie jed nostk ow e filtrów je s t łatwe i polega na w strzyknięciu filtru do testu tak, ja k wstrzykuje się każdą inną zależność usługi. Później wystarczy wywołać filtr z różnym i argum en­ tam i i sprawdzić, czy działa tak, ja k powinien, we wszystkich warunkach. W następnym rozdziale opisujem y techniki trasowania w systemie AngularJS oraz deklarowania różnych tras w aplikacji. Pokażemy, jak posługiwać się opcjonalnym modułem n g R o u te i jak za jego pom ocą kontrolować dostęp do aplikacji.

    Podsumowanie

    |

    151

    152

    |

    Rozdział 9. Testowanie jednostkowe filtrów

    ______________________________________ROZDZIAŁ 10.

    Trasowanie przy użyciu modułu ngRoute

    Do tej pory opisywaliśmy różne części systemu szkieletowego AngularJS, czyli kontrolery, usługi i filtry. Ale cały czas opieram y się na pojedynczym szablonie H T M L, zm ieniającym zachow anie w zależności od sposobu działania kontrolera lub usługi. W prawdziwej aplikacji jednostronicow ej utworzylibyśmy wiele widoków, które byłyby ładowane w odpowiedzi na kliknięcie przez użyt­ kow nika różnych odnośników bądź wpisanie przez niego określonych adresów w przeglądarce. Odtworzenie takiej funkcjonalności w czystym JavaScripcie jest trudne, ponieważ im plem entacja trasowania wymaga zabiegów takich jak: • utworzenie maszyny stanów; • dodawanie i usuwanie elem entów z historii przeglądarki; • ładowanie i usuwanie szablonów oraz odpowiedniego kodu JavaScript w odpowiedzi na zmiany stanu; • rozpoznanie charakterystycznych cech różnych przeglądarek internetowych. N ajczęściej fu nkcjonalność tę opakowuje się we wtyczki lub im plem entuje od zera. W ów czas program ista musi przeczesywać bazę kodu, aby dowiedzieć się, ja k działa i ja k z nią postępować. System AngularJS zapewnia opcjonalny m oduł o nazwie ngRoute, który m ożna wykorzystywać do trasowania w swoich aplikacjach. Zgodnie z filozofią tego systemu trasowanie jest deklaratywne, tzn. wszystkie trasy są zdefiniowane w jednej sekcji konfiguracyjnej, w której określa się samą trasę i czynności do wykonania przez system, gdy trasa ta zostanie użyta. W rozdziale tym im plem entujem y własny złożony widok w aplikacji AngularJS zawierający różne trasy oraz szczegółowo opisujem y różne opcje konfiguracji trasowania. Ponadto przedstawiamy koncepcję rozwiązań (re so lv e ) i dodajem y stronę, do której dostęp m ają tylko wybrani użytkow­ nicy w określonych warunkach. Na końcu zamieściliśmy też przegląd innych dostępnych rozwiązań w zakresie trasowania, np. u i-ro u te r.

    Trasowanie w aplikacji jednostronicowej Po pierwsze, kiedy piszemy o trasowaniu w aplikacji jednostronicow ej (ang. sin gle-page ap p lication — SPA ), to nie m am y na myśli zwykłych adresów U R L, tylko adresy z kratką i wykrzyknikiem. Na przykład zwykły adres U R L m oże wyglądać tak: h ttp ://w w w .m y a w eso m ea p p .co m /first/p a g e.

    153

    A

    w a p lik a c j i j e d n o s t r o n ic o w e j , a d r e s y n a j c z ę ś c ie j m a j ą p o s t a ć

    h ttp ://w w w .m y a w eso m ea p p .co m /

    # /first/page l u b http://w w w .m y aw esom eap p .com /# !/first/p ag e. C h o d z i o to, ż e p r z e g l ą d a r k i in a c z e j t r a k t u j ą a d r e s y z k r a t k ą n i ż z w y k łe . J e śli z o s t a n ie u ż y t y a d r e s

    h ttp ://w w w .m y aw esom eap p .co m /first/p a g e, p r z e g l ą d a r k a w y s y ł a p o d t e n a d r e s d o s e r w e r a ż ą d a n ie o d p o w i e d n i e g o d o k u m e n t u H T M L i k o d u J a v a S c r ip t . A g d y u ż y t k o w n i k p r z e j d z ie n p . p o d a d r e s

    http://w w w .m yaw esom eapp.com /secon d/page, to p r z e g lą d a r k a w y ś le k o le jn e p e łn e ż ą d a n ie d o s e r w e r a w c e lu p o b r a n i a n a s t ę p n e g o k o m p l e t n e g o d o k u m e n t u H T M L . W

    a p lik a c j i j e d n o s t r o n ic o w e j t a k i s p o s ó b d z ia ła n ia je st n ie p o ż ą d a n y i s t a r a m y się z a p o b ie g a ć p e ł n y m

    p r z e ł a d o w a n io m s tr o n . C h c e m y z a ła d o w a ć t y lk o tę c z ę ś ć d o k u m e n t u H T M L , k t ó r a je st n a m p o t r z e b ­ n a , a r e s z tę p o z o s t a w i a m y w s p o k o j u , z w ł a s z c z a j e ś li n ie z m i e n i a s ię m i ę d z y r ó ż n y m i s t r o n a m i. G d y p r z e g lą d a r k a n a t k n i e s ię n a a d r e s w r o d z a j u

    h ttp://w w w .m yaw esom eapp.com /# /first/page a lb o

    h ttp ://w w w .m y aw esom ea p p .co m /# !/first/p ag e, w y s y ł a ż ą d a n ie s e r w e r o w e d o h ttp ://w w w .m y aw e ^ so m ea p p .co m /. W s z y s t k o , c o z n a j d u j e s ię z a z n a k i e m # w ż ą d a n i u s e r w e r o w y m , je st p r z e z p r z e ­ g l ą d a r k ę i g n o r o w a n e . T o n a k l i e n c i e s p o c z y w a o b o w i ą z e k o b s ł u ż e n i a tej c z ą s t k i a d r e s u . K i e d y w ię c u ż y t k o w n i k p r z e jd z ie ze s t r o n y

    h ttp ://w w w .m y a w e s o m e a p p .c o m /# /fir s t/p a g e n a s t r o n ę

    h ttp ://w w w .m y aw esom eap p .co m /# /secon d /p a g e, p r z e g l ą d a r k a n ie w y k o n a ż a d n e g o d o d a t k o w e g o ż ą d a n i a s e r w e r o w e g o . I n n y m i s ło w y , n i e s p o w o d u j e t o p r z e ł a d o w a n i a s t r o n y . P r o c e s t e n p r z e d ­ s t a w i o n o s c h e m a t y c z n i e n a r y s u n k u 10.1.

    Rysunek 10.1. Porównanie sposobu obsługi normalnych adresów URL i adresów z kratką A p l i k a c j e j e d n o s t r o n i c o w e w y k o r z y s t u j ą fa k t, ż e a d r e s y z k r a t k ą n ie p o w o d u j ą p r z e ł a d o w a n i a s t r o n y i u ż y w a j ą c z ę ś c i z n a j d u j ą c e j s ię z a k r a t k ą d o n a w ig a c j i. G d y c z ą s t k a ta s ię z m ie n i, w łą c z a s ię s k r y p t J a v a S c r ip t , k t ó r y ł a d u j e t y l k o p o t r z e b n e d a n e i k o d H T M L , z a m ia s t p r z e ł a d o w y w a ć c a ły d o k u m e n t . D z i ę k i t e m u t r z e b a p o b r a ć m n ie j d a n y c h z s e r w e r a i a p lik a c j a d z ia ła s z y b c ie j o r a z le p ie j re a g u je n a d z ia ła n ia u ż y t k o w n ik a .

    154

    I

    Rozdział 10. Trasowanie przy użyciu modułu ngRoute

    System AngularJS do trasowania wykorzystuje adresy U RL z kratką, więc będziemy definiować właśnie takie trasy. Jeśli zatem zdefiniujem y trasę / f i r s t , to w adresie U R L cząstka /fir s t będzie dodawana za znakiem #.

    Moduł ngRoute Kiedyś m echanizm trasowania wchodził w skład rdzenia biblioteki AngularJS, ale został z niego przeniesiony do opcjonalnego m odułu, który w razie potrzeby trzeba dołączyć do aplikacji. Roz­ działu tego dokonano ze względu na pojawienie się wielu alternatywnych popularnych rozwiązań do trasowania. Dlatego w AngularJS 1.2 m echanizm trasowania stał się opcjonalnym m odułem. Aby z niego (lub z jakiegokolwiek innego zgodnego z zasadami trasowania przyjętymi w AngularJS) skorzystać, należy wykonać następujące czynności: 1. D ołącz kod źródłowy opcjonalnego modułu do kodu H TM L aplikacji. N ajczęściej wystarczy użyć poniższego znacznika H TM L: < s c r i pt t y p e = "t e x t / j a v a s c r i p t " s r c = " / ś c i e ż k a / d o / a n g u la r - r o u t e . m in . j s "> < / s c r i pt>

    2. Zdefiniuj m oduł jako zależność głównego modułu aplikacji AngularJS: a n g u la r.m o d u le ("m y A p p ",

    [ "n g R o u t e "] )

    3. Zaznacz część strony, która ma się zmieniać wraz z trasą. W przypadku modułu n g R o u t e służy do tego dyrektywa n g - v i e w , którą dodaje się do wybranego elem entu H TM L. 4. Z definiuj trasy w sekcji c o n f i g (opisanej w rozdziale 6. przy okazji konfigurow ania usługi $ h ttp )

    za pom ocą usługi $ r o u t e P r o v i d e r .

    Spójrzm y na prosty przykład aplikacji w czytującej dwa różne szablony w zależności od zmiany trasy: < title > T ra so w a n ie w system ie A n g u la rJ S < / t itle > < s c r ip t s r c = " h ttp s:/ / a ja x .g o o g le a p is .c o m / a ja x / lib s / a n g u la r js/ 1 .3 .1 1 / a n g u la r .js "> < / s c r ip t > < s c r ip t s r c = " h t t p s : / / a ja x .g o o g le a p is .c o m / a ja x / lib s / a n g u la r js/ 1 .3 .1 1 / a n g u la r -r o u te .js "> < / sc r ip t > A p lik a c ja t ra s u ją c a A ngularJS < a h r e f= "# / "> T ra s a dom yślna < a h ref="#/se co n d ">D ru ga t ra s a < / a > < / li> < a h r e f= "# / a sd a s d a s d "> N ie ist n ie ją c a t ra s a < / a > < / li>
< d iv ng-view >
< s c r ip t t y p e = "t e x t / ja v a s c rip t "> a n g u la r .m o d u le ('r o u tin g A p p ', [ 'n g R o u t e '] ) . c o n f ig ( [ '$ r o u t e P r o v id e r ', fu n c tio n ($ ro u te P ro v id e r)

{

M oduł ngRoute

|

155

$ r o u t e P r o v id e r . w h e n ( '/ ', { tem p late: 'T o j e s t dom yślna t ra s a < / h 5 > ' }) . w h e n ( '/ s e c o n d ', { tem p late : 'T o j e s t dru ga t ra s a < / h 5 > ' }) . o t h e r w is e ( { r e d ir e c t T o :

'/ '} ) ;

}]); < / s c r ip t >


Jest to bardzo prosta aplikacja z obsługą tras. Jak już napisaliśmy wcześniej, aby trasowanie w ogóle działało, należy przed jego zaimplementowaniem wykonać pewne czynności: 1. Załadować do aplikacji plik angular-route.js za plikiem angular.js, by udostępnić moduł ngRoute. 2. Zaznaczyć za pom ocą dyrektywy ng-view w kodzie H TM L obszar, który m a być zm ieniany wraz ze zm ianą trasy. 3. Przy tworzeniu własnego modułu należy zaznaczyć, że zależy on od modułu ngRoute (za p o­ m ocą kodu angular.m odule("routingA pp", ["n g R ou te"])). 4. Zdefiniować trasy w sekcji co n fig AngularJS za pom ocą dostawcy $routeProvider. 5. Trasowanie w takiej aplikacji m ożna obsługiwać przez wykorzystanie zwykłych odnośników (jak w przykładzie) lub poprzez ręczne wpisywanie adresów U RL w pasku adresu przeglądarki. M ożesz nawet spróbować, czy uda Ci się wejść na drugą trasę w bezpośredni sposób. 6. Historia przeglądanych stron zostaje zachowana, więc nadal m ożna używać przycisków W stecz i D alej przeglądarki. D ostaw ca $routeP rovid er umożliwia definiowanie tras za pom ocą funkcji when(), która przyj­ m uje dwa argumenty: • Pierwszy argum ent to adres U R L lub wyrażenie regularne reprezentujące zbiór adresów URL. • Drugi argum ent je st obiektem konfiguracyjnym określającym , co m a się wydarzyć, gdy zo­ stanie użyta określona trasa. D la uproszczenia w poprzednim przykładzie załadowaliśmy szablon H TM L wprost z tego samego dokum entu, w którym zdefiniow aliśm y ustaw ienia trasow ania. W szystko to je st bardzo proste, ale m ożna by było dodać wiązania AngularJS i inne składniki, o których była mowa we w cześniej­ szych częściach tej książki. Dodatkowe m ożliwości opisujem y w dalszej części rozdziału. Ponadto wywołaliśmy funkcję otherw ise dostawcy $routeProvid er, określającą domyślne zacho­ wanie systemu w przypadku, gdy użytkownik użyje trasy, której nie zdefiniowano w konfiguracji. Jeśli więc ktoś wejdzie np. pod adres /# /a sd a sd sa , to zostanie przekierowany pod adres /, pod któ­ rym ładow any je s t szablon dom yślnej trasy. Gdybyśm y usunęli wyw ołanie fu n k cji oth erw ise, użytkow nik zobaczyłby pustą stro n ę, poniew aż system A ngularJS nie w iedziałby, ja k i szablon H T M L załadow ać.

156

|

Rozdział 10. Trasowanie przy użyciu modułu ngRoute

Opcje trasowania W poprzednim podrozdziale pokazaliśm y definicję bardzo prostej aplikacji AngularJS wykorzy­ stującej trasowanie. Jej działanie polega tylko na ładowaniu różnych szablonów w zależności od trasy. Ale m echanizm trasowania systemu AngularJS zapewnia znacznie większe m ożliwości i p o­ zwala na definiowanie bardziej złożonych szablonów. Funkcja $routeProvider.when w pierwszym argu m en cie p o b iera adres U R L lub w yrażenie regu larn e rep rez en tu ją ce klasę adresów U R L , a w drugim — obiekt konfiguracji tras. Jej składnia je st następująca: $ r o u t e P r o v id e r . w h e n (u r l, { tem p late: s t r i n g , te m p la te U rl: s t r i n g , c o n t r o ll e r : s t r i n g , f u n c t io n o r a r ra y , c o n t r o ll e r A s : s t r i n g , r e s o lv e : o b je c t< k e y , fu n c t io n > });

Poniżej znajduje się opis opcji, których m ożna używać przy definiowaniu tras za pom ocą usługi $routeProvider. url Jest to pierwszy argum ent przekazywany do funkcji when dostawcy $routeProvider. Określa się w nim adres U R L (lub adresy URL, jeśli jest to wyrażenie regularne), dla którego m a być urucham iana konfigu racja trasy. Ponadto m ożna określić zm ienne, za pom ocą których do trasy m ożna wprowadzić identyfikator albo inne inform acje. Na przykład poprawne trasy to / l i s t , / re cip e / :re cip e Id itd. W drugiej z tych tras dostawca $routeProvid er zostaje poin­ formowany, że po części /r e c ip e / w adresie U RL będzie znajdować się zm ienna treść, którą trzeba pobrać i przekazać do kontrolera za pom ocą usługi $routeParams. Tem at ten rozsze­ rzymy jeszcze w następnym podrozdziale. tem plate Jeżeli kod szablonu H T M L nie je s t zbyt obszerny, to m ożna go w pisać ja k o łań cu ch bez­ pośred nio w obiekcie kon fig u racji trasy przekazyw anym ja k o drugi argum ent do fu nkcji $routeP rovid er.w hen. System A ngularJS b ezpośred nio wstawi ten szablon do dyrektywy ng-view. templateUrl Kod H TM L poszczególnych widoków często jest bardzo skom plikowany i obszerny. W ta­ kich przypadkach lepiej jest szablon każdego widoku wstawić do osobnego pliku i przekazać adres U R L tego pliku w argum encie tem p lateU rl. System AngularJS załaduje wskazany plik z serwera, gdy będzie trzeba wyświetlić określoną trasę. Przyszłe żądania tego szablonu będą obsługiwane z lokalnego bufora, aby ograniczyć liczbę odwołań do serwera. Poniżej znajduje się przykład definicji tego argumentu: $ r o u t e P r o v id e r . w h e n ( '/ t e s t ', { te m p la te U rl: 'v i e w s / t e s t . h t m l', });

D efinicja ta nakazuje pobranie pliku v iew s/test.htm l z serwera i załadowanie go do dyrektywy ng-view.

Opcje trasowania

|

157

c o n tr o lle r Kontroler dla wybranej trasy m ożna zdefiniować na dwa sposoby. Pierwsza możliwość polega na użyciu opcjonalnego argumentu c o n tr o lle r funkcji $routeProvider.when, którego używa się w przypadku, gdy bezpośrednio w kodzie H TM L nie zostanie zdefiniowany kontroler za pomocą dyrektywy ng-control l er. Jeśli kontroler zostanie zadeklarowany za pomocą składni angul arApp. ^ co n tro lle r("M y C trl"), to można podać jego nazwę jako łańcuch. Istnieje też możliwość używa­ nia składni control l erAs dyrektywy ng-control l er, więc można również napisać MyCtrl as c t r l . Druga opcja polega na zdefiniowaniu kontrolera śródliniowo — w tym przypadku funkcję kontrolera przekazuje się bezpośrednio do klucza kontrolera. Można także użyć składni tablicowej do wstrzykiwania zależności bez obawy przed „obrzydzaniem” kodu. Może to wyglądać tak: $ r o u t e P r o v id e r . w h e n ( '/ t e s t ', { tem p late: '< h 1 > T ra sa t e sto w a < / h 1 > ', c o n t r o ll e r : [ '$ w in d o w ', fu n ctio n ($ w in d o w ) { $ w in d o w .a le r t('T e s to w a t r a s a z o s t a ł a z a ła d o w a n a !') ; }] });

W przykładzie tym definiujemy trasę ze śródliniowym kontrolerem zależnym od usługi $window. Jedynym zadaniem trasy jest wyświetlenie okna alertu przy ładowaniu dokumentu. Do defi­ niowania kontrolerów zaleca się używanie składni c o n tro lle r. D efinicja śródliniowa utrudnia pisanie testów jednostkow ych i wielokrotne wykorzystanie kodu. co n tro llerA s Klucz co n tro ll erAs jest udogodnieniem pozwalającym zdefiniować nazwę kontrolera poza klu­ czem c o n tro lle r. Dwie poniższe definicje trasy są równoważne pod względem funkcjonalności: $ r o u t e P r o v id e r . w h e n ( '/ t e s t ', { tem p late: 'T e sto w a t r a s a < / h 1 > ', c o n t r o ll e r :

'M y C tr l as c t r l '

}); $ r o u t e P r o v id e r . w h e n ( '/ t e s t ', { tem p late: 'T e sto w a t r a s a < / h 1 > ', c o n t r o ll e r : 'M y C t r l ', c o n t r o ll e r A s : ' c t r l ' });

Nie m a większej różnicy, czy zdefiniujemy nazwę kontrolera w kluczu control l er, czy w osobnym kluczu control l erAs. W ybór jednej z tych metod jest jedynie kwestią osobistych preferencji. red irectT o Czasami trasom zmienia się nazwy oraz tworzy się różne adresy URL, które w istocie odpowiadają tej samej stronie. Wówczas można użyć klucza redirecTo, wskazującego adres URL, do którego m a prowadzić określona trasa. Jest to przydatne w obsłudze błędów i przekierowań. Na przykład: $ r o u t e P r o v id e r . w h e n ('/ n e w ', { tem p late: '
< d iv n g - r e p e a t = "s t o c k in m a in C t r l. s t o c k s " > < d iv s to c k -w id g e t > < / d iv > < /d i v> < / d iv > < s c r i pt s r c = " h t t p s :/ / a j a x . g o o g le a p is . c o m / a ja x / li b s / a n g u la r j s / 1 . 3 . 1 1 / a n g u l a r . j s " > < / s c r i pt> < s c r i p t s r c = " a p p . j s " > < / s c r i pt> < s c r i p t s r c = " d i r e c t i v e . j s " > < / s c r i pt>

Zm ieniła się tylko zawartość bloku n g-rep eat, w którym teraz znajduje się element
. Na koniec przedstawiamy definicję samej dyrektywy: / / Plik: r11/directive-with-template/directive.js a n g u la r . m o d u le ( 's t o c k M a r k e t A p p ') . d ir e c t i v e ( 's t o c k W i d g e t ', [ f u n c t io n ( ) retu rn { te m p la te U rl: 's t o c k . h t m l'

{

}; }]);

W pliku tym utworzyliśmy bardzo prostą dyrektywę. Inform ujem y system AngularJS, że za każ­ dym razem, gdy napotka w kodzie H TM L dyrektywę o nazwie stock-w idget (nadaliśmy je j nazwę stockWidget, a w kodzie H TM L zm ienia się ona na stock-w idget), m a pobrać szablon sto ck.h tm l z naszego serwera i wstawić go do elementu, na którym ta dyrektywa została użyta. System AngularJS jest inteligentny i szablon wskazany w polu templateUrl pobierze tylko raz, przy pierwszym napotkaniu dyrektywy stock-widget. Następnie zapisze go w swoim lokalnym buforze i stamtąd będzie go serwować. . ys -Ł r ’

r

Jeśli kodu H T M L je s t niew iele, m ożna go wpisać bezpośrednio w obiekcie definicji dyrektywy, w kluczu templ ate. Poniżej znajduje się przykład zastosowania tego rozwiązania dla pliku stock.htm l: a n g u la r . m o d u le ( 's t o c k M a r k e t A p p ') . d ir e c t i v e ( 's t o c k W i d g e t ', [ f u n c t io n ( ) { retu rn { tem p late : '< d i v c l a s s = " s t o c k - d a s h " > ' + 'Nazwa: ' + '< s p a n c la s s = " s t o c k - n a m e " ' + 'n g - b in d = "s t o c k . n a m e "> ' + '< / s p a n > ' + 'C en a : ' + '< s p a n c l a s s = " s t o c k - p r i c e " ' + 'n g - b i n d = " s t o c k . p r i c e | c u r r e n c y "> ' + '< / s p a n > ' + 'Zm ian a: ' + '< s p a n c l a s s = " s t o c k - c h a n g e " ' + 'n g - b in d = "m a in C t r l.g e t C h a n g e ( s t o c k ) + ' % ' " > ' + '< / s p a n > ' + '< / d i v > ' }; }]);

Opcje podstawowe

|

187

Ten kod może zastąpić poprzednią definicję dyrektywy. Obie wersje w interfejsie użytkownika będ ą działały identycznie, tzn. będ ą ładow ać szablon H T M L do elem entu, na którym została użyta dyrektywa. W ykorzystanie klucza t e m p la t e m a sens, gdy kod H TM L jest niezbyt obszerny i niezbyt kłopotli­ wy w obsłudze. N atom iast większe i bardziej skomplikowane szablony zawsze lepiej jest ładować za pom ocą klucza t e m p l a t e U r l .

Określanie sposobu użycia dyrektywy Słowo kluczowe r e s t r i c t służy do określania sposobu, w jak i m ożna używać dyrektywy w kodzie. Jak napisaliśmy wcześniej, domyślnie dyrektywy można stosować jako atrybuty elementów H TM L (w przykładzie wykorzystaliśmy elem ent < d i v s t o c k - w i d g e t > < / d i v > ). Ale podczas tworzenia dyrektywy m ożna wybrać sposób je j używania. O to lista dopuszczalnych w artości klucza r e s t r i c t : A

Litera A oznacza, że dyrektywę m ożna stosow ać jak o a try b u t istniejącego elem entu H TM L (np. < d i v s t o c k - w i d g e t > < / d i v > ). Jest to w artość domyślna. E

Litera E oznacza, że dyrektywy m ożna używać jako nowego elem en tu H T M L (np. < s t o c k ^ w i d g e t > < / s t o c k - w i d g e t > ). C

Litera C oznacza, że dyrektywę m ożna wykorzystywać jako nazwę klasy elementu H TM L (np. c l a s s = " s t o c k - w i d g e t " > < / d i v > ).

< d iv M

Litera M oznacza, że dyrektywy m ożna używać jako kom entarza H TM L (np. < ! - - d i r e c t i v e : s t o c k - w id g e t

- - > ).

Kiedyś taki tryb był potrzebny dla dyrektyw obejmujących wiele elementów,

np. wierszy w tabeli. Ale teraz do tego celu służą dyrektywy n g - r e p e a t - s t a r t i n g - r e p e a t - e n d i zaleca się używanie ich zamiast dyrektyw kom entarzowych. Każdą z tych op cji m ożna stosow ać pojedynczo i w połączeniu z innym i. Poniżej znajduje się zm ieniona definicja dyrektywy s t o c k - w i d g e t , którą m ożna wykorzystywać zarówno jako atrybut, ja k i samodzielny element: // Plik: r11/directive-with-restrict/directive.js a n g u la r . m o d u le ( 's t o c k M a r k e t A p p ') . d i r e c t i v e ( 's t o c k W i d g e t ', [ f u n c t io n ( ) retu rn { te m p la te U rl: 's t o c k . h t m l ', r e s t r i c t : 'A E '

{

}; }]);

Dodaliśmy do obiektu definicji dyrektywy klucz r e s t r i c t o wartości A E . W ten sposób sprawiliśmy, że dyrektywy m ożna używać i jako atrybutu H TM L, np. < d i v < s t o c k - w id g e t > .

nieważ nie zdefiniowaliśmy odpowiedniej opcji.

188

|

s t o c k - w id g e t > ,

i jako elem entu

Nie m ożna natom iast posługiwać się trybem < d i v c l a s s = " s t o c k - w i d g e t " > , po­

Rozdział 11. Dyrektywy

Wyrażenia w dyrektywach klasowych

'

*

Opisaliśmy cztery możliwe wartości klucza r e s t r ic t . W następnych podrozdzia­ łach opisujemy sposoby przekazywania wartości do dyrektyw, ale pewnie już teraz zastanawiasz się, jak to możliwe w dyrektywach klasowych. A mówiąc konkretniej, jak wyrazić
za pomocą klasy? Dyrektywy klasowe mają postać
i system AngularJS traktuje je podobnie jak przekazanie wartości do atrybutu w HTML.

Z n a s z j u ż r ó ż n e s p o s o b y w y k o r z y s t a n i a d y r e k t y w , w ię c c z a s d o w i e d z i e ć się , j a k ie s ą n a j le p s z e p r a k t y k i ic h u ż y w a n ia : • P r z e g l ą d a r k a In t e r n e t E x p l o r e r d o w e r s j i 8. w ł ą c z n i e n ie l u b i n i e s t a n d a r d o w y c h e l e m e n t ó w w k o d z i e H T M L . Je śli z a m ie r z a s z je s t o s o w a ć , m u s i s z tę p r z e g lą d a r k ę p o i n f o r m o w a ć o t y m , że t w o r z y s z n o w e e le m e n ty , z a p o m o c ą w y w o ł a n i a t y p u d o c u m e n t . c r e a t e E le m e n t ( ' s t o c k - w id g e t ') . A n g u l a r J S o d w e r s j i 1.3 n i e o b s ł u g u j e p r z e g l ą d a r k i I n t e r n e t E x p l o r e r 8 (a r a c z e j n ie je st z n i ą te sto w a n y ). • D y r e k t y w y k l a s o w e s ą i d e a ln e d o r e n d e r o w a n i a ( t a k j a k d y r e k t y w a n g - c l o a k , u k r y w a j ą c a i p o k a z u j ą c a e le m e n t y , a lb o d y r e k t y w y d o ł a d o w a n i a o b r a z ó w ) . • D y r e k t y w w p o s t a c i e l e m e n t ó w z a le c a s ię u ż y w a ć , g d y t w o r z y s ię k o m p l e t n ą n o w ą t r e ś ć H TM L. • D y r e k t y w y a t r y b u t o w e n a j le p ie j j e s t w y k o r z y s t y w a ć d o i m p l e m e n t o w a n i a m o d y f i k a t o r ó w z a c h o w a ń ( j a k n g - s h o w , n g - c l a s s itd .).

Funkcja link S ło w o k lu c z o w e l i n k w o b ie k c ie d e f in ic j i d y r e k t y w y s łu ż y d o d o d a w a n ia „ f u n k c j i łą c z ą c e j”. F u n k c j a t a je st d la d y r e k t y w y t y m , c z y m k o n t r o l e r d la w i d o k u —

d e f in iu j e A P I i f u n k c j e p o t r z e b ­

n e d y re k t y w ie d o w y k o n y w a n ia i n n y c h c z y n n o ś c i o b o k m a n ip u lo w a n ia s t r u k t u r ą D O M . S y s t e m A n g u l a r J S w y k o n u j e f u n k c j ę l i n k d la k a ż d e g o e g z e m p la r z a d y r e k t y w y , w ię c k a ż d y e g z e m ­ p l a r z m a w ł a s n ą k o m p l e t n ą lo g i k ę b iz n e s o w ą , k t ó r a n ie w p ł y w a n a lo g ik ę p o z o s t a ł y c h e g z e m p la r z y . F u n k c j i l i n k p r z e k a z u j e s ię s t a n d a r d o w y z e s t a w a r g u m e n t ó w , k t ó r y w y g l ą d a tak: lin k :

f u n c t io n ( $ s c o p e , $elem ent, $ a t t r s )

{}

P i e r w s z y a r g u m e n t to z a k r e s e le m e n t u , n a k t ó r y m p r a c u je d y r e k t y w a . D r u g i o k r e ś l a e le m e n t D O M , n a k t ó r y m p r a c u j e d y r e k t y w a . A t r z e c i a r g u m e n t z a w ie r a a t r y b u t y e le m e n t u j a k o ł a ń c u c h y . Je śli t r z e b a d o d a ć f u n k c j o n a l n o ś ć d o e g z e m p l a r z a d y r e k t y w y , m o ż n a t o z r o b i ć w z a k r e s ie e le m e n t u , z k t ó r y m p ra c u je m y . P o s ł u g u j ą c s ię p o p r z e d n i m p r z y k ł a d e m , p r z e n i e s i e m y f u n k c j o n a l n o ś ć z k o n t r o l e r a m a i n C t r l d o n a s z e j d y r e k t y w y , d z i ę k i c z e m u jej d z i a ł a n i e n i e b ę d z i e u z a l e ż n i o n e o d o b e c n o ś c i k o n t r o l e r a M a in C t r l z f u n k c j ą o n a z w ie ge tC h a n ge :

/ / Plik: r11/directive-with-link/directive.js a n g u la r . m o d u le ( 's t o c k M a r k e t A p p ') . d ir e c t i v e ( 's t o c k W i d g e t ', [ f u n c t io n ( ) re tu rn { te m p la te U rl: 's t o c k . h t m l ',

{

Opcje podstawowe

|

189

r e s t r i c t : 'A E ', l i n k : f u n c t io n ( $ s c o p e , $elem ent, $ a t t r s ) { $ sco p e .ge tC h a n ge = f u n c t io n ( s t o c k ) { r e t u r n M a t h . c e i l ( ( ( s t o c k . p r i c e - s t o c k . p r e v io u s ) / s t o c k . p r e v io u s ) * 100 ); }; } }; }]);

W kodzie tym definicja dyrektywy zawiera funkcję lin k , dodającą funkcję o nazwie getChange() do zakresu tej dyrektywy. D zięki tem u każdy je j egzem plarz będzie m iał w sw oim zakresie tę funkcję. Fun kcja ta została przeniesiona bez żadnych zm ian do definicji dyrektywy z kontrolera. Teraz zobaczmy, ja k użylibyśmy j ej w pliku stock.h tm l: < d iv c l a s s = " s t o c k - d a s h " > Nazwa: Cena: Zmiana procentow a: < / d iv >

Z m iany w pliku s to c k .h tm l są nieliczn e i dotyczą tylko ostatn iej dyrektywy ng-bind. Z am iast mainCtrl .getC hange(stock) wiążemy getC han ge(stock), co oznacza, że dyrektywa szuka funkcji getChange() w swoim własnym zakresie. Teraz, niezależnie od tego, w którym kontrolerze użyje­ m y naszej dyrektywy (m ożemy nawet zmienić jego nazwę), dyrektywa stockWidget będzie miała własną wersję tej funkcji. Krótko mówiąc, stanie się niezależna. W funkcji lin k m ożna też definiować własne procedury nasłuchowe, pracować bezpośrednio na elementach D O M i wykonywać wiele innych działań. Szerzej na ten temat piszemy w rozdziale 13.

^

Czym jest zakres Pewnie zastanawiasz się, co to jest zakres, do którego dodajemy funkcje. Masz się czym martwić, bo o zakresie trzeba zawsze pamiętać, gdy dodaje się do niego funkcje za pomocą funkcji link. W przykładzie przedstawionym w tym podrozdziale dyrektywa ng-repeat użyta w pliku index.html tworzy zakres dla każdego towaru znajdującego się w tablicy i to właśnie do tego zakresu dodajemy też nasze funkcje. Z tego powodu nie zmieniamy zakresu kontrolera bezpośrednio, tylko jako skutek uboczny użycia dyrek­ tywy ng-repeat. Gdybyśmy skorzystali z naszej dyrektywy poza blokiem ng-repeat, to modyfikowalibyśmy zakres kontrolera bezpośrednio, co nie jest dobrą praktyką. Zakres domyślny nadawany w funkcji link (chyba że programista zdecyduje inaczej) jest zakresem, który ma rodzic. Dodawanie funkcji do zakresu nadrzędnego zawsze jest niemile widziane, ponieważ zakres nadrzędny nie powinien być zmieniany z po­ ziomu potomka.

190

|

Rozdział 11. Dyrektywy

Nadal pozostaje zależność od nazwy zm iennej stock , której zm iana spowoduje usterkę interfejsu użytkownika. W następnym punkcie pokazujemy, ja k pozbyć się i tej zależności.

Zakres D om yślnie każda dyrektywa dziedziczy zakres po rodzicu, który to zakres je st przekazywany je j w fu nkcji lin k . To m oże powodować następujące problem y: • D odaw anie zm ien n y ch i fu n k cji do zakresu pow oduje zm ianę także rodzica, który nagle otrzym uje dostęp do większej liczby zm ien ny ch i fu n k cji. • Dyrektywa może niecelowo przesłonić istniejącą funkcję lub zm ienną o takiej samej nazwie. • Dyrektywa może niepostrzeżenie zacząć używać zmiennych i funkcji z rodzica. To może powo­ dować problemy, jeśli zm ienimy nazwy własności w rodzicu, a nie zrobimy tego w dyrektywie. W obiekcie definicji dyrektywy m ożna stosować klucz scope, który daje program iście pełną kon ­ trolę nad zakresem elem entu dyrektywy. Kluczowi temu m ożna przypisać jed ną z trzech wartości: fa ls e Jest to wartość dom yśln a, oznaczająca, że zakres jest taki sam jak nadrzędny, jakikolwiek on jest. W efekcie dyrektywa otrzymuje prawo dostępu do wszystkich zmiennych i funkcji zdefiniowa­ nych w zakresie nadrzędnym, a wszelkie poczynione przez nią zmiany są widoczne w rodzicu. tru e W arto ść ta oznacza, że dyrektywa dziedziczy zakres nadrzędny, ale tworzy własny zakres potom ny. W efekcie dyrektywa otrzymuje prawo dostępu do wszystkich zm iennych i funkcji z zakresu nadrzędnego, ale poczynione przez nią zm iany nie są widoczne w rodzicu. U sta­ wienie to jest z a leca n e, gdy potrzebny jest dostęp do funkcji i inform acji rodzica, lecz trzeba w nich wprowadzić lokalne m odyfikacje. ob iekt Ew entualnie w argum encie zakresu m ożna też przekazać obiekt z kluczam i i w artościam i. W ówczas system AngularJS tworzy tzw. izolow any zakres (ang. iso la ted scope) — n ie dzie­ dziczy on niczego z rodzica, a wszelkie dane, które zakres nadrzędny m a użytkować wspólnie z tą dyrektywą, m uszą zostać przekazane przez atrybuty H TM L. Jest to n a jlep sz a op cja dla kom ponentów , które muszą być niezależne od tego, ja k i gdzie są używane. W obiekcie m ożna określić atrybuty, które pow inny zostać zastosowane w kodzie H TM L, gdy wykorzystywana je st dana dyrektywa, oraz typy w artości, które zostaną do niej przekazane. Do wyboru są trzy typy wartości, które AngularJS doda bezpośrednio do zakresu dyrektywy:

Znak = sygnalizuje, że wartość atrybutu w H TM L należy traktować jako obiekt JSO N , który zostanie związany z zakresem dyrektywy, dzięki czemu wszelkie zmiany wprowadzone w za­ kresie nadrzędnym będą automatycznie widoczne w dyrektywie. @ Znak @ sygnalizuje, że wartość atrybutu H TM L m a być traktowana jako łańcuch, który może zawierać wyrażenia wiązania AngularJS { { } } . W arto ść zostanie obliczona i do zakresu dy­ rektywy zostanie przypisana ostateczna wartość. W szelkie zm iany w artości będą widoczne także w dyrektywie.

Opcje podstawowe

|

191

& Znak & sygnalizuje, że wartość atrybutu w H TM L jest funkcją z jakiegoś kontrolera, który musi być dostępny dla dyrektywy. Wówczas dyrektywa może wywołać tę funkcję w razie potrzeby. Aby nasza dyrektywa była w pełni sam odzielna i nadawała się do wielokrotnego użytku, m ożemy teraz przekazać obiekt sto ck do naszego widżetu. Dzięki tem u jeśli nazwa zm iennej na zewnątrz ulegnie zm ianie, będzie m ożna przekazać do dyrektywy tę now ą nazwę i w ten sposób uzyskać niezależność od nazwy. Pom ysł ten m ożem y zrealizować przy użyciu znaku = z obiektem zakresu oraz możemy przypisać wartość do wybranej zmiennej w izolowanym zakresie dyrektywy. Najpierw zobaczmy, ja k zm ieni się sposób wykorzystania dyrektywy w pliku in dex.htm l: < t i t le > A p li k a c j a g i e td o w a < / t itle > < d iv n g - c o n t r o ll e r = " M a in C t r l as m a in C t r l" > < h 3 > L is t a towarów < d iv n g - r e p e a t = "s in m a in C t r l. s t o c k s " > < d iv s to c k -w id g e t s t o c k - d a t a = " s " > < / d iv > < s c r i p t s r c = " h t t p s :/ / a j a x . g o o g leapi s . c o m / a j a x / lib s / a n g u la r js / 1 . 3 . 1 1 / a n g u l a r . j s " > < / s c r ip t > < s c r i p t s r c = " a p p . j s " > < / s c r i pt> < s c r i p t s r c = " d i r e c t i v e . j s " > < / s c r i pt>

Jedyna zm iana w tym pliku znajduje się w dyrektywie ng-repeat i naszej dyrektywie. Zm ieniliśm y nazwę pętli sto ck in m a in C trl.sto ck s na s in m a in C trl.sto ck s. Nasza dyrektywa m a teraz po­ stać sto c k -d a ta = "s " i zasadniczo służy nam jak o m etoda przekazania interesu jącej nas wartości w jednolity sposób. Teraz, niezależnie od tego, jak a będzie nazwa, np. stock, s albo xyz, przeka­ żemy ją do naszego widżetu za pom ocą klucza stock -d ata. Zobaczmy, ja k to jest zaim plem entowane w samej dyrektywie: // Plik: r11/directive-with-scope/directive.js a n g u la r . m o d u le ( 's t o c k M a r k e t A p p ') . d ir e c t i v e ( 's t o c k W i d g e t ', [ f u n c t io n ( ) retu rn { te m p la te U rl: 's t o c k . h t m l ', r e s t r i c t : 'A ', scope: { s to c k D a ta : ' = '

{

}, l i n k : f u n c t io n ( $ s c o p e , $elem ent, $ a t t r s ) { $ sco p e .ge tC h a n ge = f u n c t io n ( s t o c k ) { r e t u r n M a t h . c e i l ( ( ( s t o c k . p r i c e - s t o c k . p r e v io u s ) / s t o c k . p r e v io u s ) * 100 ); }; } }; }]);

192

|

Rozdział 11. Dyrektywy

W kodzie tym zdefiniowaliśmy izolowany zakres przez przekazanie obiektu do klucza scope obiektu definicji dyrektywy. W obiekcie tym zdefiniowaliśmy klucz stockD ata o wartości =. Skutki tego są następujące: • W zakresie izolowanym dyrektywy została utworzona zm ienna o nazwie stockData. • W kodzie H TM L wartość zmiennej stockData m ożna ustawiać za pom ocą atrybutu stock-data. • W artość zm iennej stockD ata jest z w ią z a n a z obiektem wskazywanym przez atrybut H TM L stock-data. Każda zmiana wartości w kontrolerze od razu znajduje odzwierciedlenie w zmiennej stockD ata w zakresie dyrektywy. Zobaczm y teraz, ja k zm ienił się plik stock.h tm l: < d iv c l a s s = " s t o c k - d a s h " > Nazwa: Cena: Zmiana procentow a: < / d iv >

W kodzie H TM L wszystkie odwołania do zm iennej sto ck rodzic zostały zam ienione na własny egzemplarz dyrektywy stockData. W tym m om encie kod H TM L i dyrektywa nie zależą już od kontekstu, w którym są używane, po­ nieważ wszystkie dane i cała logika potrzebne dyrektywie są w niej (dzięki funkcji lin k ) albo do niej przekazywane (za pom ocą zakresu izolowanego). Gdybyśmy teraz w dyrektywie ng-repeat pozostawili oryginalną nazwę sto ck (zam iast s) i w k o­ dzie H TM L używali nazwy stock zamiast stockD ata, otrzymalibyśm y pustą wartość. Jest tak dla­ tego, że izolacja zakresu powoduje, iż dyrektywa traci dostęp do zawartości swojego rodzica, gdyż zostaje usunięta z norm alnej hierarchii zakresów. Gdy przekazujem y dane za pom ocą wiązania obiektów z dyrektywami, są one przekazywane przez referencję. System AngularJS wykorzystuje ten fakt do tego, aby modyfikacje zm iennej do­ konane w kontrolerze były widoczne w dyrektywie. Ale oznacza to też, że jeśli w dyrektywie refe­ rencja do zm iennej zostanie użyta w przypisaniu, to wiązanie danych w AngularJS ulegnie awarii. Spójrzmy na przykład. Najpierw zmniejszymy liczbę towarów w tablicy oraz dodamy do kontrolera m etodę tworzącą wszystkie nowe obiekty towarów o nazwie changeA llStocks i m etodę o nazwie ch an geFirstStock, zm ieniającą nazwę tylko pierwszego towaru: / / Plik: r11/directive-broken-reference/app.js a n g u la r . m o d u le ( 's t o c k M a r k e t A p p ', [ ]) . c o n t r o l l e r ( 'M a i n C t r l ', [ f u n c t io n ( ) var s e lf = t h is ;

{

Opcje podstawowe

|

193

s e lf. s t o c k s = [ {name: 'P ie r w s z y t o w a r ', p r ic e : 100, p r e v io u s : 2 2 0 }, {name: 'D r u g i t o w a r ', p r ic e : 140, p r e v io u s : 120} ]; s e lf . c h a n g e A l lS t o c k s = f u n c t io n ( ) f o r ( v a r i = 0; i < 4; i+ + ) { s e lf . s t o c k s [ i] = { name: 'T o w ar k o n t r o l e r a ', p r ic e : 200, p r e v io u s : 250

{

}; } }; s e lf . c h a n g e F i r s t S t o c k = f u n c t io n ( ) { s e lf . s t o c k s [ 0 ] . n a m e = 'Z m ie n io n o p ie rw sz y t o w a r '; }; }]);

Plik in d ex .h tm l jest podzielony na dwie części. Pierwsza wyświetla listę towarów za pom ocą dy­ rektyw s t o c k - w i d g e t i n g - r e p e a t , a druga wyświetla te towary za pom ocą indywidualnych in ­ strukcji. Na tym przykładzie chcem y pokazać, jaki wpływ dodatkowy zakres dyrektywy n g - r e p e a t wywiera na w iązanie danych. P onad to dodaliśm y dwa przyciski u ru ch am iające now e fu nkcje zdefiniowane w kontrolerze: < t i t le > A p li k a c j a g i e łd o w a < / t itle > < d iv n g - c o n t r o ll e r = " M a in C t r l as m a in C t r l" > < h 3 > L is t a towarów w y św ie tlo n a au to m atyczn ie < d iv n g - r e p e a t = "s in m a in C t r l. s t o c k s " > < d iv s to c k -w id g e t s t o c k - d a t a = " s " > < / d iv > < h 3 > L is t a towarów w y św ie tlo n a rę c z n ie < / h 3 > < d iv s to c k -w id g e t s t o c k - d a t a = " m a in C t r l. s t o c k s [ 0 ] " > < d iv s to c k -w id g e t s t o c k - d a t a = " m a in C t r l. s t o c k s [ 1 ] " > < b u tto n n g - c l i c k = " m a i n C t r l . c h a n g e A ll S t o c k s ( ) "> Zmień w sz y s k ie tow ary z k o n t r o le r a < b u tto n n g - c l i c k = "m a in C t rl.c h a n g e F i r s t S t o c k ( ) " > Zmień p ie rw sz y tow ar z k o n t r o le r a < s c r i p t s r c = " h t t p s :/ / a j a x . g o o g leapi s .c o m / a ja x / lib s / a n g u l a r j s / 1 . 3 . 11/angul a r . j s " > < / s c r ip t > < s c r i p t s r c = " a p p . j s " > < / s c r i pt> < s c r i p t s r c = " d i r e c t i v e . j s " > < / s c r i pt>

194

I

Rozdział 11. Dyrektywy

W dyrektywie też dodajemy podobną funkcję zm ieniającą bieżący towar — o nazwie c h a n g e S t o c k : / / Plik: r11/directive-broken-reference/directive.js a n g u la r . m o d u le ( 's t o c k M a r k e t A p p ') . d i r e c t i v e ( 's t o c k W i d g e t ', [ f u n c t io n ( ) retu rn { te m p la te U rl: 's t o c k . h t m l ', r e s t r i c t : 'A ', sco pe : { sto c k D a ta : '= '

{

}, l i n k : f u n c t io n ( $ s c o p e , $elem ent, $ a t t r s ) { $sco p e .ge tC h a n ge = f u n c t io n ( s t o c k ) { r e t u r n M a t h . c e i l ( ( ( s t o c k . p r i c e - s t o c k . p r e v io u s ) s t o c k . p r e v io u s ) * 100);

/

}; $ s c o p e .c h a n g e S to c k = f u n c t io n ( ) $ s c o p e .s to c k D a ta = { name: 'T o w ar d y re k ty w y ', p r ic e : 500, p r e v io u s : 200

{

}; }; } }; }]);

W pliku stock.h tm l dodajem y przycisk wywołujący funkcję c h a n g e S t o c k dyrektywy: < d iv c l a s s = " s t o c k - d a s h " > Nazwa: Cena: Zmiana procentow a: Zmień tow ar w d y re k ty w ie < / d iv >

W aplikacji tej wypróbujemy kilka ścieżek wykonawczych: 1.

N ajpierw k likn ij jed en z dw óch przycisków zdefiniow anych w głów nym kodzie H T M L (zm ieniający wszystkie towary lub tylko jed en). Później kliknij jed en z indywidualnych (albo tylko pierwszy, jeśli kliknięto przycisk Z m ień p ierw szy to w a r z k o n tro lera ) przycisków przy towarach. Gdy to zrobisz, najpierw interfejs zm ieni się w reakcji na kliknięcie przycisku wy­ wołującego funkcję z kontrolera, a następnie przez kliknięcie jednego z indywidualnych przy­ cisków. W szystko dzieje się zgodnie z oczekiwaniami.

Opcje podstawowe

|

195

2. Później kliknij przycisk pierwszego towaru z sekcji pętli, a po nim przycisk Z m ień p ierw sz y to w a r z k o n tr o le r a . K likn ięcie pierwszego przycisku spow oduje zaktualizow anie w artości towaru, natom iast kliknięcie drugiego przycisku nie wywoła żadnego efektu. Jest tak, p o ­ nieważ podczas gdy poprzednia aktualizacja z kontrolera przedostała się do dyrektywy, w dy­ rektywie zm ieniana je st referencja w zakresie n g - r e p e a t (przypisujem y ponow nie w artość do $ s c o p e . s t o c k D a t a ), a nie rzeczywista zm ienna w kontrolerze. Dzięki temu wszelkie zm ia­ ny w dyrektywie stają się niewidoczne w kontrolerze. Pomyśl o tym tak: gdyby kontroler od­ woływał się do refe re n cji R1, to dyrektywa początkow o rów nież wskazyw ałaby R1. Ale po dodaniu $ s c o p e . s t o c k D a t a

=

s o m e t h in g

dyrektywa odnosi się do referencji R2, która jest

zm ieniana w zakresie n g - r e p e a t . N atom iast kontroler nadal przechowuje referencję do R1. Zatem teraz zm iana w R1 nie m a wpływu na R2 i dlatego s t o c k - w i d g e t nadal pokazuje stare dane, m im o że je zm ieniam y w kontrolerze. 3. Spróbujemy też wykonać dokładnie takie same czynności jak powyżej, ale na pierwszym towarze w sekcji ręcznego powtarzania. W tym przypadku program zachowa się zgodnie z naszymi ocze­ kiwaniami. Jest tak, ponieważ zm ieniając wartość w dyrektywie, odnosimy się do rzeczywistej zmiennej w kodzie H TM L (m a in C t r l . s t o c k s [ 0 ] ), a nie do zmiennej z zakresu bloku n g - r e p e a t . Zatem zmiana wewnątrz dyrektywy jest prawidłowo propagowana do oryginalnej zmiennej. 4. N a koniec najpierw klikniem y przycisk indywidualnego towaru, a następnie — przycisk Z m ień w szystkie tow ary z kon trolera. Sprawi to, że wpierw zostanie zaktualizowany indywidu­ alny towar, a następnie wszystkie towary otrzym ają taką samą nazwę. Powodem, dla którego to działa, choć wcześniej nie działało, jest to, że w n g - r e p e a t m am y listę towarów. Gdy zm ie­ nim y referencję z kontrolera, n g - r e p e a t uruchom i się jeszcze raz i utworzy nowe egzemplarze dyrektywy z praw idłow ą referen cją . D latego w łaśnie w tym przypadku w szystko zadziała, a wcześniej nie działało. Pam iętaj zatem o przypisywaniu zm iennych w swojej dyrektywie, zwłaszcza jeśli są one używane w bloku n g - r e p e a t . Istnieje ryzyko, że dojdzie do uszkodzenia stanu, jeżeli m echanizm wiązania danych zmodyfikuje zm ienne w nieodpowiednim zakresie. T eraz rozszerzymy naszą dyrektywę o pozostałe dwa argumenty zakresu. Chcemy przekazać łańcuch z naszego kontrolera i otrzymać punkt zaczepienia z naszej dyrektywy, aby wywoływać funkcję w na­ szym kontrolerze za każdym razem , gdy użytkownik kliknie przycisk w egzemplarzu dyrektywy. Zrobim y to krok po kroku. N ajpierw spójrzm y na kontroler: // Plik: r11/directive-with-scope-advanced/app.js angul a r . m o d u le ( 's t o c k M a r k e t A p p ', []) . c o n t r o l l e r ( 'M a i n C t r l ', [ f u n c t io n ( ) { v a r s e lf = t h is ; s e lf. s t o c k s = [ {name: 'P ie r w s z y t o w a r ', p r ic e : 100, p r e v io u s : 2 2 0 }, {name: 'D r u g i t o w a r ', p r ic e : 140, p r e v io u s : 120 }, {name: 'T r z e c i t o w a r ', p r ic e : 110, p r e v io u s : 110}, {name: 'C z w a rty t o w a r ', p r ic e : 400, p r e v io u s : 420} ]; s e lf . o n S t o c k S e le c t = f u n c t io n ( p r ic e , name) { c o n s o le . lo g ( 'W y b r a n a cena ', p r ic e , 'Nazwa ', }; }]);

196

|

Rozdział 11. Dyrektywy

name);

Dodaliśmy do naszego kontrolera funkcję, która ma być wywoływana za każdym razem, gdy użytkownik wybierze towar. Funkcja ta jest wywoływana z ceną i nazwą wybranego towaru, które zapisuje w konsoli. Funkcję ge tC h a n g e usunęliśmy z tego kontrolera, kiedy przenieśliśmy ją do funkcji l i n k dyrektywy: < h tm l> < t i t l e > A p l i k a c j a g ie łd o w a < / t it le > < d iv n g - c o n t r o ll e r = " M a in C t r l as m a in C t r l"> < h 3 > L is t a towarów < d iv n g - r e p e a t = "s in m a in C t r l. s t o c k s " > < d iv s to c k -w id g e t sto c k -d a ta = "s" s t o c k - t it le = " T o w a r { { $ i n d e x } } . { { s . n a m e } } " w h e n - s e le c t = "m a in C t r l. o n S t o c k S e le c t ( s t o c k P r ic e , < / d iv > < /d i v> < / d iv >

sto ckN am e )">

< s c r i pt s r c = " h t t p s :/ / a j a x . g o o g le a p is . c o m / a ja x / li b s / a n g u la r j s / 1 . 3 . 1 1 / a n g u l a r . j s " > < / s c r i pt> < s c r i p t s r c = " a p p . j s " > < / s c r i pt> < s c r i p t s r c = " d i r e c t i v e . j s " > < / s c r i pt>


W pliku in d ex .h tm l zm ieniliśm y sposób użycia widżetu s t o c k - w i d g e t . O prócz danych towarów przekazujemy jeszcze tytuł i funkcję, która ma być wywoływana, gdy zostanie wybrany jakiś towar. Atrybut s t o c k - t i t l e pobiera łańcuch, w którym dozwolona jest interpolacja (obsługuje składnię { { } } ),

więc m ożem y mu przekazać kom binację zwykłego tekstu i wartości zm iennych. Atrybut

w h e n - s e le c t

pobiera referencję do funkcji, którą wywołuje wraz z nazwami parametrów tej funkcji.

Param etry te nazwaliśmy s t o c k P r i c e i s t o c k N a m e . Nazwy te są ważne, o czym przekonasz się przy definiow aniu ich w kodzie H TM L. Zauważ, że w od różn ieniu od dyrektyw n g - b i n d i n g - s h o w , w których in stru k cja m a i n C t r l . o n S t o c k S e l e c t ( ) spow odow ałaby wyw ołanie fu n k cji, atrybut w h e n -s e le c t

tylko tworzy referencję do funkcji dostępną naszej dyrektywie i to m y decydujemy,

kiedy funkcja ta zostanie wywołana: < d iv c l a s s = " s t o c k - d a s h " > Nazwa: Cena: Zmiana procentow a < / d iv >

216

I

Rozdział 13. Zaawansowane opcje definicji dyrektyw

K ontroler także jest taki sam ja k w rozdziale 11.: / / Plik: r13/directive-no-transclusion/app.js a n g u la r . m o d u le ( 's t o c k M a r k e t A p p ', [ ]) . c o n t r o l l e r ( 'M a i n C t r l ', [ f u n c t io n ( ) { var s e lf = t h is ; s e lf. s t o c k s = [ {name: 'P ie r w s z y t o w a r ', p r ic e : 100, p r e v io u s : 2 2 0 }, {name: 'D r u g i t o w a r ', p r ic e : 140, p r e v io u s : 120}, {name: 'T r z e c i t o w a r ', p r ic e : 110, p r e v io u s : 110 }, {name: 'C z w a rty t o w a r ', p r ic e : 400, p r e v io u s : 420} ]; }]);

Tylko w pliku in d ex .h tm l wprowadzamy drobne zmiany, ale jedynie w sposobie wykorzystania utw orzonej przez nas dyrektywy: < h tm l> < t i t l e > A p l i k a c j a g ie t d o w a < / t it le > < d iv n g - c o n t r o ll e r = " M a in C t r l as m a in C t r l" > < h 3 > L is t a towarów < d iv n g - r e p e a t = "s in m a in C t r l. s t o c k s " > < d iv s to c k -w id g e t s t o c k - d a t a = " s " > Ta t r e ś ć z o s t a n ie w yrzucona. < / d iv > < /d i v> < / d iv > < s c r i pt s r c = " h t t p s :/ / a j a x . g o o g le a p is . c o m / a ja x / li b s / a n g u la r j s / 1 . 3 . 1 1 / a n g u l a r . j s " > < / s c r i pt> < s c r i p t s r c = " a p p . j s " > < / s c r i pt> < s c r i p t s r c = " d i r e c t i v e . j s " > < / s c r i pt>


W pliku tym w dyrektywie s t o c k - w i d g e t wpisaliśmy trochę treści, aby dostarczyć własnych danych do renderowania. Jeśli teraz uruchomisz aplikację (pamiętaj, żeby n ajp ierw u ru ch om ić lokaln y serw er, jak w poprzed­ nich rozdziałach), zauważysz, że treść dodana w elemencie z naszą dyrektywą w pliku in d ex .h tm l zostanie zignorowana i załadowana zostanie tylko zawartość klucza t e m p la t e . Jest to dom yśln e za­ chowanie każdej dyrektywy AngularJS, polegające na tym, że jeżeli zdefiniowany jest klucz t e m p la t e lub t e m p l a t e U r l , dyrektywa usuwa treść elementu, dla którego została zadeklarowana, i zastępuje ją zawartością tych kluczy. W większości przypadków jest to pożądane zachowanie, ale jeśli chcemy dodać własny kod H TM L w dyrektywie (pomyśl o akordeonach i widżetach do wyświetlania treści na kartach), to wolelibyśmy tego uniknąć. Jeżeli chcemy, żeby system AngularJS honorował zarówno treść elementu, dla którego została zadeklarowana dyrektywa, ja k i szablon tej dyrektywy, m usim y wykorzystać tran sklu zję.

Transkluzja

| 217

Podstawy transkluzji W najprostszym ujęciu transkluzję m ożna sobie wyobrazić jako dwuetapowy proces: 1. N ajpierw inform ujem y dyrektywę, że zamierzamy w niej skorzystać z transkluzji. Stanowi to sygnał dla systemu AngularJS, że jeśli napotka tę dyrektywę w kodzie H TM L, należy wykonać kopię zawartości jej elementu, by nie została utracona podczas wstawiania szablonu dyrektywy. W tym celu w obiekcie definicji dyrektywy ustawiamy klucz tran sclu d e na tru e. 2. Następnie wskazujemy systemowi AngularJS, gdzie m a wstawić treść szablonu. Do tego służy dyrektywa n g -tra n s c l ude, pow od u jąca wstaw ienie zapisanej treści w elem encie szablonu dyrektywy. Zobaczm y, jakie zm iany należy wprowadzić w kodzie z poprzedniego przykładu, aby wykorzystać w nim transkluzję. Kontroler pozostaje niezmieniony, więc go nie powtarzamy. W pliku in dex.htm l potrzebne są pewne zmiany, ponieważ elem ent z dyrektywą stock-w idget m usi zawierać jakąś treść H TM L: < t i t le > A p li k a c ja g i e id o w a < / tit le > < d iv n g - c o n t r o ll e r = " M a in C t r l

as m a in C t r l" >

< h 3 > L is t a towarów < d iv n g - r e p e a t = "s in m a in C t r l. s t o c k s " > < d iv s to c k -w id g e t s t o c k - d a t a = " s " > P o le c a n y tow ar: {{s.n a m e }} < / d iv > < s c r i pt s r c = " h t t p s :/ / a j a x . g o o g le a p i s . co m /a jax /li b s / a n g u la r j s / 1 . 3 . 1 1 / a n g u l a r . j s " > < / s c r i pt> < s c r i p t s r c = " a p p . j s " > < / s c r i pt> < s c r i p t s r c = " d i r e c t i v e . j s " > < / s c r i pt>

Pierwsza zm iana w definicji dyrektywy jest przedstawiona poniżej: // Plik: r13/directive-transclusion/directive.js a n g u la r . m o d u le ( 's t o c k M a r k e t A p p ') . d i r e c t i v e ( 's t o c k W i d g e t ', [ f u n c t io n ( ) retu rn { te m p la te U rl: 's t o c k . h t m l ', r e s t r i c t : 'A ', t ra n s c lu d e : tru e , scope: { sto c k D a ta : ' = '

{

}, l i n k : f u n c t io n ( $ s c o p e , $elem ent, $ a t t r s ) { $ sco p e .ge tC h a n ge = f u n c t io n ( s t o c k ) { r e t u r n M a t h . c e i l ( ( ( s t o c k . p r i c e - s t o c k . p r e v io u s ) /

218

|

Rozdział 13. Zaawansowane opcje definicji dyrektyw

stock.previous) * 100); }; } }; }]);

W tej definicji dyrektywy dodaliśmy klucz tran sclu d e o wartości tru e. To jedyna zm iana w tej definicji. Teraz spójrzm y na zawartość pliku sto ck .h tm l, która również nieco się zm ienia: < d iv c l a s s = " s t o c k - d a s h " > < / sp a n > Cena:
| c u r re n c y ">

n g -b in d = "g e tC h a n g e (s to c k D a t a ) + ' % ' " >
< / d iv >

Usunęliśm y nazwę i wiązanie dla stockData.name, a w zm ian dodaliśmy elem ent z dyrek­ tywą n g -transclu d e. Efekt tych zm ian jest następujący: • Gdy system AngularJS napotka dyrektywę stock-w idget w pliku in d ex .h tm l, pobierze je j we­ wnętrzną treść (Polecany towar: {{s.n a m e}}) i zapisze ją na później. • Po dodaniu do egzemplarza szablonu dyrektywy system poszuka dyrektywy n g -tran sclu d e i wstawi treść zapisaną w pierwszym punkcie do elem entu z dyrektywą n g -tran sclu d e. • Później, podczas renderowania każdego towaru z tablicy towarów, najpierw zostanie wyświe­ tlony tekst P olecan y tow ar, a po nim nazwa towaru.

^

Zakres dyrektywy n g - t r a n s c l ude Pewnie zastanawiasz się, jak działa dyrektywa ng-transclude, zwłaszcza że wyizo­ lowaliśmy zakres naszej dyrektywy, podczas gdy treść ng-transclude odnosi się bezpośrednio do czegoś, co znajduje się w zakresie dyrektywy ng-repeat. Kiedy system AngularJS napotyka transcl ude, kopiuje kod HTML przed zastąpieniem go treścią wskazywaną przez klucz template lub templateUrl. Gdy później napotka dyrektywę ng-transclude, kompiluje transkludowaną treść, ale łączy ją z zakresem nadrzędnym zamiast z izolowanym zakresem dyrektywy. W ten sposób transkludowana treść zachowuje dostęp do kontrolera nadrzędnego i jego treści, podczas gdy kod HTML dyrektywy ma zakres izolowany (bądź ewentualnie nowy zakres). Zatem treść transkludowaną i treść dyrektywy łączy równorzędna relacja, ale nie dzielą one tego samego zakresu. Jest tak dlatego, że treść transkludowana nie po­ winna znać wewnętrznych mechanizmów działania dyrektywy, lecz powinna zale­ żeć od kontekstu, w którym jest używana.

Transkluzja

| 219

Transkluzja — techniki zaawansowane W poprzednim przykładzie opisaliśmy następujący przypadek: mieliśmy treść związaną z dyrektywą i umieściliśmy ją w elemencie, dla którego zadeklarowaliśmy tę dyrektywę. Do realizacji tego zada­ nia wykorzystaliśmy słowo kluczowe t r a n s c l u d e i dyrektywę n g - t r a n s c l u d e w naszej dyrektywie. Inną typową sytuacją, gdy używa się dynamicznej treści zależnej od użytkownika, jest utworzenie licznych kopii szablonu i wykorzystywanie ich w razie potrzeby. Na przykład zamiast wyświetlać zawartość dyrektywy jeden raz, m ożem y chcieć pokazać ją kilka razy w karuzeli. Podobnie działa dyrektywa n g - r e p e a t , w której definiujem y szablon do zwielokrotnienia, a potem dla każdego eg­ zemplarza tworzymy ten szablon i dynamicznie wstawiamy go do naszej dyrektywy. Zobaczm y, ja k to zrobić przy użyciu własności t r a n s c l u d e naszego obiektu definicji dyrektywy. W następnym przykładzie utworzymy prostą dyrektywę m ogącą zastąpić dyrektywę n g - r e p e a t , która będzie pobierać pewne zm ienne z zewnętrznego zakresu i dodawać pewne zm ienne dla każdego egzemplarza. Nie zaim plem entujem y autom atycznej aktualizacji, tylko skupim y się na wykorzystaniu transkluzji do renderowania egzemplarzy naszego szablonu. N ajpierw zajrzymy do pliku app.js, który się nie zmienia: // Plik: r13/directive-advanced-transclusion/app.js angul a r . m o d u le ( 's t o c k M a r k e t A p p ', []) . c o n t r o l l e r ( 'M a i n C t r l ', [ f u n c t io n ( ) { v a r s e lf = t h is ; s e lf. s t o c k s = [ {name: 'P ie r w s z y t o w a r ', p r ic e : 100, p r e v io u s : 2 2 0 }, {name: 'D r u g i t o w a r ', p r ic e : 140, p r e v io u s : 120 }, {name: 'T r z e c i t o w a r ', p r ic e : 110, p r e v io u s : 110}, {name: 'C z w a rty t o w a r ', p r ic e : 400, p r e v io u s : 420} ]; }]);

Zawartość tego pliku pozostaje niezm ienna. Teraz zajrzymy do pliku in d ex .h tm l, która również uchyla rąbka tajem nicy na tem at sposobu wykorzystania dyrektywy: < t i t le > A p li k a c j a g i e łd o w a < / t itle > < d iv n g - c o n t r o ll e r = " M a in C t r l as m a in C t r l" > < h 3 > L is t a towarów < d iv s i m p le - s t o c k - r e p e a t = " m a in C t r l. s t o c k s " > Z n a le z io n o tow ar {{s t o c k .n a m e }} pod indeksem { { c u r r e n t In d e x } } < /d i v> < s c r i pt s r c = " h t t p s :/ / a j a x . g o o g le a p i s . co m /a jax /li b s / a n g u la r j s / 1 . 3 . 1 1 / a n g u l a r . j s " > < / s c r i pt> < s c r i p t s r c = " a p p . j s " > < / s c r i pt> < s c r i p t s r c = " d i r e c t i v e . j s " > < / s c r i pt>

220

I

Rozdział 13. Zaawansowane opcje definicji dyrektyw

Usunęliśm y dyrektywę stock-w idget i zamiast ng-repeat użyliśmy naszej nowej dyrektywy o na­ zwie sim p le -sto ck -re p e a t. Pobiera ona tablicę z naszego kontrolera i wykorzystuje je j zawartość w szablonach. Dyrektywa ta eksponuje bieżący elem ent jako zm ienną sto ck oraz dodatkowe in ­ form acje, takie ja k indeks elementu w zm iennej currentIndex. Otwierając tę stronę w przeglądar­ ce, oczekujemy, że napis Z n a lez io n o ... zostanie wydrukowany cztery razy — po jednym razie dla każdego towaru — wraz z inform acją o indeksie. Teraz zobaczmy, ja k wygląda definicja sam ej dyrektywy: / / Plik: r13/directive-advanced-transclusion/directive.js a n g u la r . m o d u le ( 's t o c k M a r k e t A p p ') . d i r e c t i v e ( 's im p l e S t o c k R e p e a t ', [ f u n c t io n ( ) { re tu rn { r e s t r i c t : 'A ',

// Przechwytuje i zastępuje cały element, a nie tylko jego zawartość. t r a n s c lu d e :

'e le m e n t ',

/ / $transclude przekazujem y ja k o piąty argument do fu n kcji link. l i n k : f u n c t io n ( $ s c o p e , $elem ent, $ a t t r s , c t r l , $ t r a n s c lu d e ) v a r m yArray = $ s c o p e . $ e v a l( $ a t t r s . s im p le S t o c k R e p e a t ) ; v a r c o n t a in e r = a n g u la r.e le m e n t( '< d i v c l a s s = " c o n t a i n e r " > < / d i v > ') ; f o r (v a r i = 0; i < m y A rra y .le n g t h ;

i+ + )

{

{

/ / Tworzy egzemplarz elementu z nowym zakresem potom nym za pom ocą fu n kcji łączącej klonu. v a r in s t a n c e = $ t r a n sc lu d e ($ s c o p e .$ n e w (), fu n c t io n (c lo n e d E le m e n t , newScope) {

// eksponuje własne zm ienne dla egzemplarza n e w Sc o p e .c u rre n tIn d e x = i ; n e w Scop e .stock = m y A r r a y [ i] ; });

/ / dodaje egzemplarz do naszego kontenera c o n t a in e r .a p p e n d ( in s t a n c e ) ; }

/ / Ustawienie transclude: ’elem ent’pow oduje zam ianę elementu na komentarz. / / Wygenerowana treść zostanie dodan a za tym komentarzem. $ e le m e n t . a f t e r ( c o n t a in e r ) ; } }; }]);

W dyrektywie tej wprowadziliśmy kilka now ości, więc teraz przeanalizujemy je jed na po drugiej: • Pierw sza zm iana dotyczy klucza tra n sc lu d e obiektu d efin icji dyrektywy. W poprzednim przykładzie ustawiliśmy go na wartość tru e, nakazującą systemowi AngularJS pobranie i za­ chowanie treści elem entu, do którego została zastosowana dyrektywa. Jeśli klucz tran sclu d e zostanie ustaw iony na w artość elem ent, oznacza to, że należy skopiow ać cały elem ent wraz z wszystkimi dyrektywami, które m ogą być w nim zadeklarowane. • Funkcja lin k , ja k pokazaliśm y w rozdziale 11., domyślnie pobiera trzy argumenty: zakres dy­ rektywy, elem ent, w którym zadeklarowano dyrektywę, oraz atrybuty tego elementu. D odat­ kowo m ożna przekazać kontrolery dyrektywy w czwartym argumencie. Ale teraz najbardziej interesuje nas piąty argument, będący funkcją transkluzji generowaną tylko wtedy, gdy w obiek­ cie definicji dyrektywy użyto klucza tra n scl ude.

Transkluzja

| 221

Funkcja tran sclu d e jest konstruktorem pozwalającym tworzyć nowe egzemplarze naszego szablonu tyle razy, ile trzeba w danym przypadku. Funkcja ta pobiera opcjonalnie zakres (jeśli dla elem entu potrzebny jest now y zakres; w przeciwnym razie dziedziczy zakres dyrektywy) i obowiązkowo funkcję łączącą klonu. • W pierwszym wierszu obliczamy wartość zmiennej wymienionej w kodzie H TM L, aby otrzy­ m ać uchwyt do tablicy, którą chcemy przeglądać. W tym celu wywołujemy funkcję $eval zakresu z łańcuchem zawierającym kod JavaScript, który chcem y wykonać w kontekście tego zakresu. • Jako że ustawienie element klucza tran sclu d e powoduje skopiowanie całego elem entu, ele­ m ent ten zostaje również usunięty z kodu H TM L. W związku z tym tworzymy elem ent k o n ­ tenerow y do wstawiania wszystkich utworzonych przez nas egzemplarzy. • N astępnie urucham iam y pętlę f o r dla każdego egzem plarza w naszej tablicy i wywołujem y funkcję tran sclu d e przekazaną do funkcji łączącej. Zwraca ona nowy elem ent H TM L będący skompilowaną i poskładaną wersją naszego szablonu, którą m ożna wstawić do dokumentu. • Jak w spom nieliśm y w cześniej, pierwszym argum entem fu nkcji tra n sclu d e je st op cjonalny zakres. W naszym przykładzie tworzymy nowy zakres potom ny. Robim y to po to, aby doko­ nywane w nim modyfikacje nie były widoczne w zakresie nadrzędnym. Zapobieganie m ie­ szaniu się stanów globalnych jest d o b ry m z w y cz a jem . • Drugi argum ent funkcji tran sclu d e to funkcja łącząca dla sklonowanego elementu. W funk­ cji tej definiujem y logikę i zm ienne charakterystyczne dla danego egzemplarza szablonu (jak w naszym przypadku currentIndex i stock ). • Później dodajem y utworzony egzemplarz szablonu do elementu kontenerowego i w końcu wstawiamy kontener za egzemplarzem dyrektywy (który w tym przypadku jest tylko kom en­ tarzem w kodzie H TM L). Bez tego kroku nasze skompilowane i gotowe do współpracy z AngularJS elem enty D O M nie pojawiłyby się na stronie. Ogólnie rzecz biorąc, techniki transkluzji m ożna używać zawsze, gdy potrzebny jest kom ponent, którego szablon i interfejs użytkownika mogą się zm ieniać w zależności od sposobu wykorzystania i kontekstu. W podjęciu decyzji, czy skorzystać z transkluzji, pomoże C i poniższa lista: • Czy każdy użytkownik dyrektywy musi określić własny szablon lub własną logikę renderowania? Jeśli tak, użyj funkcji tran sclu d e. • Czy ważna jest tylko treść dyrektywy, czy potrzebny jest też elem ent, w którym dyrektywę tę się deklaruje? W pierwszym przypadku zastosuj ustawienie tra n sclu d e : tru e, a w drugim — tra n sclu d e : element. • Jeśli trzeb a tylko wyświetlić tanskludow aną tre ść bez je j m odyfikow ania, użyj dyrektywy n g -tra n s c l ude bezpośrednio w szablonie swojej dyrektywy. • Czy trzeba wygenerować wiele kopii szablonu lub dodać zachowania, zm ienne albo logikę biznesową do zakresu, którego dotyczy transkluzja? Jeżeli tak, wstrzyknij funkcję transkluzji do swojej funkcji lin k . • W ywołaj funkcję transkluzji z opcjonalnym nowym zakresem (zalecane) i funkcją łączącą dla egzemplarza. W funkcji łączącej dodaj funkcje i zm ienne potrzebne szablonowi. Przy tw orzeniu dyrektyw do realizacji tak ich elem entów ja k karuzela czy m echanizm nieskoń­ czonego przewijania transkluzja może znacznie ułatwić pracę.

222

|

Rozdział 13. Zaawansowane opcje definicji dyrektyw

Kontrolery dyrektyw i funkcja require W rozdziale 11. wprowadziliśmy funkcję lin k dla dyrektyw, która jest idealnym miejscem na zacho­ wania i logikę biznesową dyrektyw. Ale jeśli dokładnie przejrzysz kod źródłowy systemu AngularJS albo dokumentację dyrektyw tego systemu (https://docs.an gu larjs.org/gu ide/directive), to zauważysz, że dyrektywy um ożliwiają też definiowanie kontrolerów. Powodem, dla którego do definiowania zachowań dyrektyw wykorzystujemy funkcję lin k , a nie kontrolery, jest to, że kontrolery dyrektyw służą po prostu do czegoś całkiem innego. K ontrolery dyrektyw w AngularJS przeznaczone są do kom unikacji między dyrektywami, podczas gdy funk­ cja lin k jest kom pletną jednostką przynależną do konkretnego egzemplarza dyrektywy. K om uni­ kacja między dyrektywami polega na tym, że dyrektywa zadeklarowana dla jednego elementu odwołuje się do innej dyrektywy zadeklarowanej dla elem entu nadrzędnego lub tego samego ele­ mentu. Czynność ta może być wykonywana w celu podzielenia się stanem , zm iennym i albo nawet funkcjam i. M etodę tworzenia kontrolerów dyrektyw prześledzimy na przykładzie dyrektywy o nazwie tab s, służącej do tworzenia widżetu z treścią prezentowaną na kartach. O prócz nadrzędnej dyrektywy tab s utworzymy dodatkowo dyrektywę tab do reprezentowania indywidualnych kart. Oczywiście dyrektywa tab będzie m ogła być używana tylko w kontekście zestawu kart. Ponadto każda karta m usi w jakiś sposób inform ow ać rodzica, że została wybrana, aby ten wyświetlił i ukrył odpo­ wiednie elem enty interfejsu. N ajpierw spójrzm y na kontroler naszej aplikacji: / / Plik: r13/directive-controllers/app.js a n g u la r . m o d u le ( 's t o c k M a r k e t A p p ', [ ]) . c o n t r o l l e r ( 'M a i n C t r l ', [ f u n c t io n ( ) var s e lf = t h is ;

{

s e lf . s t a r t e d T im e = new D a t e ( ) . g e t T im e () ; s e lf. s t o c k s = [ {name: 'P ie r w s z y t o w a r ', p r ic e : 100, p r e v io u s : 22 0 }, {name: 'D r u g i t o w a r ', p r ic e : 140, p r e v io u s : 120}, {name: 'T r z e c i t o w a r ', p r ic e : 110, p r e v io u s : 110 }, {name: 'C z w a rty t o w a r ', p r ic e : 400, p r e v io u s : 420} ]; }]);

D efinicja kontrolera aplikacji znajduje się w pliku app.js, którego zawartość niewiele się zm ieniła w porów naniu z poprzednim i przykładami. Nadal jest w nim definicja listy towarów, ale dodali­ śmy też nową zm ienną, noszącą nazwę startedTim e. W artości wszystkich tych zm iennych zosta­ ną pokazane na kartach w interfejsie użytkownika. Teraz spójrzm y na sposób użycia dyrektyw tab s i tab w pliku in d ex .h tm l: < h tm l> < t i t l e > A p l i k a c j a g ie t d o w a < / t it le > < l i n k r e l = " s t y l e s h e e t " h r e f = "m a in . c s s ">

Kontrolery dyrektyw i funkcja require

| 223

< d iv n g - c o n t r o ll e r = " M a in C t r l as m a in C t r l" > < ta b s> To j e s t p ie rw sz a k a rt a . D ata w łą c z e n ia a p l i k a c j i : { {m a in C t r l. s t a r t e d T im e To j e s t d ru ga k a rt a < d iv n g - r e p e a t = "s t o c k in m a in C t r l. s t o c k s " > Nazwa tow aru: {{s to c k .n a m e }} < / ta b s> < s c r i pt < s c r ip t < s c r ip t < s c r ip t

| d a te }}

s r c = " h t t p s :/ / a j a x . g o o g le a p i s . co m /a jax /li b s / a n g u la r j s / 1 . 3 . 1 1 / a n g u l a r . j s " > < / s c r i pt> s r c = " a p p . j s " > < / s c r i pt> s r c = "t a b s . js "> < / s c r ip t > s r c = " t a b . j s " > < / s c r i pt>

Zdefiniow aliśm y elem ent
, zn ajd u jący się pod k o n tro lą k o n tro lera M ainC trl, w którym chcem y wyświetlić dwie karty. Pierwsza nazywa się Pierwsza k arta, a je j kliknięcie powoduje wy­ świetlenie daty i godziny uruchom ienia aplikacji. Inform acje te pochodzą z kontrolera MainCtrl. Druga karta nazywa się Druga karta i przedstawia nazwy towarów z tablicy sto ck s, zdefiniowanej w kontrolerze M ainCtrl. W iesz już, ja k używać widżetu, więc czas przyjrzeć się jego budowie. Zaczniem y od dyrektywy tab s, która służy jako kontener dla zestawu kart: // Plik: r13/directive-controllers/tabs.js a n g u la r . m o d u le ( 's t o c k M a r k e t A p p ') . d i r e c t i v e ( 't a b s ', [ f u n c t io n ( ) { retu rn { r e s t r ic t : 'E ', t ra n s c lu d e : tru e , scope: tru e , tem p late: '< d i v c l a s s = " t a b - h e a d e r s " > ' + ' < d iv n g - r e p e a t = "t a b in t a b s " ' + ' n g - c lic k = "s e le c t T a b ( $ in d e x ) "' + ' n g - c l a s s = " { s e l e c t e d : is S e le c t e d T a b ( $ in d e x ) } " > ' + ' < / s p a n > ' + ' < / d iv > ' + '< / d i v > ' + '< d i v n g - t r a n s c lu d e > < / d iv > ', c o n t r o ll e r : fu n c t io n ( $ s c o p e ) { v a r c u r re n t In d e x = 0; $ s c o p e .t a b s = [ ] ; t h is . r e g is t e r T a b = f u n c t i o n ( t i t l e , scope) { i f ( $ s c o p e . t a b s .le n g t h === 0) { s c o p e . s e le c t e d = tru e ; } e ls e { s c o p e . s e le c t e d = f a l s e ; } $ s c o p e . t a b s . p u s h ({ t it le :

t it le ,

scope: s c o p e } ) ;

};

224

|

Rozdział 13. Zaawansowane opcje definicji dyrektyw

$ s c o p e .s e le c t T a b = f u n c t io n ( in d e x ) { c u r re n t In d e x = in d e x ; f o r ( v a r i = 0; i < $ s c o p e . t a b s . le n g t h ; i+ + ) { $ s c o p e . t a b s [ i] . s c o p e . s e l e c t e d = c u r re n t In d e x === i; } }; $ s c o p e . is S e le c te d T a b = fu n c t io n ( in d e x )

{

r e t u r n c u r re n t In d e x === in d ex; }; } }; }]);

W dyrektywie tab s wykorzystaliśmy koncepcję transkluzji oraz zdefiniowaliśmy kontroler. Przyj­ rzyjm y się dokładnie je j najciekawszym częściom: • Skorzystano z transkluzji (ale nie na poziom ie elementu) do pobierania indywidualnych kart i dodania im tytułów. • Dyrektywa tab s m a też zdefiniow any własny zakres, ponieważ m usi dodawać do zakresu pewne funkcje, które nie pow inny kolidow ać z w łasnościam i i funkcjam i rodzica. • Klucz tem plate zawiera definicję kodu H TM L pojedynczej karty (przechowywanej w tablicy w zakresie). Ponadto szablon obsługuje kliknięcia kart, ja k również wyróżnianie zaznaczonej karty za pom ocą funkcji należących do zakresu dyrektywy. • Szablon dyrektywy tab s definiuje elem ent
, do którego wstawiana będzie treść za po­ m ocą dyrektywy ng-tran sclu d e. Jest to kontener na treść H TM L dyrektywy (wszystkie indy­ widualne karty), która zostanie do niego wstawiona po uruchom ieniu aplikacji. • N astępnie zam iast fu nk cji l i nk d efiniujem y kontroler dyrektywy. R obim y tak, bo chcem y, aby dyrektywy potom ne dyrektywy tab s m iały dostęp do funkcjonalności tej dyrektywy. D e­ finicja kontrolera dyrektywy jest dobrym rozwiązaniem zawsze wtedy, gdy trzeba umożliwić kom unikację między dyrektywą nadrzędną i dyrektywami podrzędnymi lub między dyrek­ tywami równorzędnymi. • Kontroler dyrektywy jest funkcją pobierającą zakres i element. Funkcja ta jest podobna do funkcji li nk, ale różni się od niej tym, że funkcje zdefiniowane w kontrolerze dla th is są do­ stępne dla kontrolerów potom nych i równorzędnych (niebawem dowiesz się, w jak i sposób). Zatem c o n t r o l l e r m oże definiow ać fu nkcje specyficzne dla egzem plarza dyrektywy przez zdefiniowanie ich dla $scope, ja k to robiliśm y do tej pory, oraz może udostępniać interfejs API złożony z funkcji i zm iennych zdefiniowanych dla t hi s , czyli egzemplarza kontrolera. • W tym przypadku na obiekcie scope zdefiniowaliśmy zm ienną tab s i funkcje sele ctT a b oraz isS e le cte d , wykorzystywane przez szablon dyrektywy do wybierania i wyróżniania wybranych kart. Obie te funkcje ustawiają i kasują wartość zm iennej s e le c te d dla indywidualnej karty i w ten sposób sterują wyświetlaniem i ukrywaniem kart. • Ponadto na egzemplarzu kontrolera zdefiniowaliśmy funkcję o nazwie re g iste rT a b . Jako że funkcja ta nie jest zdefiniowana na zakresie, nie jest ona dostępna z kodu H TM L dyrektywy. Dodaje ona tytuł i zakres do tablicy, która jest stosowana do wyświetlania listy kart.

Kontrolery dyrektyw i funkcja require

| 225

Teraz przyjrzymy się dyrektywie t a b , która podczepia się do dyrektywy t a b s i wykorzystuje zde­ finiowany przez nas kontroler: // Plik: r13/directive-controllers/tab.js a n g u la r . m o d u le ( 's t o c k M a r k e t A p p ') . d i r e c t i v e ( 't a b ', [ f u n c t io n ( ) { retu rn { r e s t r ic t : 'E ', t ra n s c lu d e : tru e , tem p late: '< d i v n g - s h o w = "s e le c t e d " n g - t r a n s c lu d e > < / d iv > ', r e q u ir e : ' ' t a b s ' , scope: tru e , l i n k : fu n c t io n ( $ s c o p e , $elem ent, $ a t t r , t a b C t r l) t a b C t r l. r e g i s t e r T a b ( $ a t t r . t i t l e , $ s c o p e ) ;

{

} }; }]);

Dyrektywa t a b jest znacznie prostsza: 1. Pierwszą czynnością dyrektywy t a b jest zdefiniow anie transkluzji, ponieważ zawiera własną definicję szablonu. 2. W szablonie ( t e m p la t e ) za pom ocą dyrektywy n g - t r a n s c l u d e wstawiliśmy treść do elementu < d iv > ,

a także dodaliśmy do niego warunek pozwalający selektywnie ukrywać i pokazywać go

w zależności od wartości zm iennej s e l e c t e d z zakresu. 3. D odaliśm y nowy klucz o nazwie r e q u i r e i wartości '' t a b s (znaczenie symbolu ' wyjaśniliśmy w punkcie „O pcje klucza require”). Klucz ten inform uje system AngularJS, że aby dyrektywa tab

działała, jed en z je j elementów nadrzędnych w kodzie H TM L m usi być dyrektywą t a b s ,

oraz że chcemy, by kontroler dyrektywy t a b s był dostępny dla dyrektywy t a b . 4. Zdefiniowaliśm y nowy zakres dla tej dyrektywy, dzięki czemu je j zm ienne lokalne nie będą kolidować z elem entam i zakresu nadrzędnego. 5. W funkcji l i n k wykorzystujemy kontroler przekazany do niej w czwartym argumencie (po za­ kresie, elemencie i atrybutach). Jest to egzemplarz kontrolera zdefiniowanego przez nas w dy­ rektywie t a g s , który system AngularJS wstrzykuje dynamicznie na podstawie tego, co znajdzie. 6. W funkcji l i n k rejestrujem y kartę za pom ocą funkcji zdefiniowanej w nadrzędnej dyrektywie tab s.

Teraz przepływ sterowania w aplikacji jest następujący: 1. Gdy w kodzie H TM L zostanie znaleziona dyrektywa t a b s (nadrzędna), następuje transkluzja treści i zarezerwowanie m iejsca dla kart w dokum encie (za pom ocą dyrektywy n g - r e p e a t ). Karty są wstawiane poniżej. 2. Każda karta jest rejestrow ana w rodzicu, dzięki czemu nadrzędny kontroler kart jest in fo r­ m ow any o tym , która karta je st aktualnie zaznaczona, i m oże ją wyróżnić oraz wyświetlić, a pozostałe karty ukryć. 3. Każda karta wykorzystuje transkluzję w odniesieniu do swojej treści, którą pakuje w kontener, aby m óc ją ukrywać i pokazywać w zależności od potrzeby.

226

|

Rozdział 13. Zaawansowane opcje definicji dyrektyw

4. Przy rejestracji kontroler dyrektywy tab s ustawia pierwszą kartę jako wybraną (przy użyciu zm iennej zakresu przekazanego do kontrolera). 5. Później dyrektywa n g - c l i c k obsługuje ukrywanie i wyświetlanie indywidualnych kart za po­ m ocą funkcji zdefiniowanych w zakresie dyrektywy tabs.

Opcje klucza require Słowo kluczowe req u ire w obiekcie definicji dyrektywy jako w artość może przyjąć łańcuch lub tablicę łańcuchów reprezentujących nazwy dyrektyw, które m uszą zostać użyte wraz z bieżącą dyrektywą. N a przykład: r e q u ir e :

't a b s '

Ten kod nakazuje systemowi AngularJS znalezienie dyrektywy tab s udostępniającej kontroler na tym samym elem encie, do którego została zastosowana bieżąca dyrektywa. Analogicznie: r e q u ir e :

[ 't a b s ',

'n g M o d e l']

T en kod inform uje A ngularJS, że w elem encie z bieżącą dyrektywą potrzebne są dyrektywy tab s i ng-model. Jeśli w r e qui r e zostanie przekazana tablica, fu n kcja l i nk w czwartym argum encie otrzyma tablicę kontrolerów, a nie tylko jed en kontroler. Każdy łań cu ch m oże m ieć pewne przed rostki określające sposób traktow ania przez system A ngularJS znalezionej dyrektywy. N a przykład: r e q u ir e :

'? t a b s '

Ten kod nakazuje systemowi AngularJS znalezienie dyrektywy tabs w tym samym elemencie i po­ woduje przekazanie wartości null w czwartym argumencie do funkcji l i nk, jeśli szukana dyrek­ tywa nie zostanie znaleziona. Innym i słowy, przedrostek ? oznacza zależność opcjonalną. Ewentualnie m ożna nakazać systemowi AngularJS, aby szukał dyrektywy wyżej w hierarchii. Służy do tego poniższy przykład: r e q u ir e :

'^ t a b s '

Jest to konstrukcja z poprzedniego przykładu, w którym poinform ow aliśm y system A ngularJS, że dyrektywa tab s musi być zadeklarowana w jednym z elem entów nadrzędnych (niekoniecznie m usi to być bezpośredni przodek). Przedrostków tych m ożna też używać w kom binacjach, np.: r e q u ir e :

'? ^ t a b s '

Ten kod oznacza, że elem ent nadrzędny naszej dyrektywy może, ale nie m usi m ieć zadeklarowa­ nej dyrektywy tab s, która w razie jeśli jest dostępna, powinna zostać wstrzyknięta do funkcji l i nk naszej dyrektywy.

Kontrolery dyrektyw i funkcja require

| 227

Dyrektywy wejściowe i ng-model W poprzednim podrozdziale pokazaliśmy, jak się tworzy kontrolery dyrektyw oraz jak się je wyko­ rzystuje do kom unikacji między dyrektywami i w celu wymiany inform acji o stanie. W tym punk­ cie zastosujem y zdobytą wiedzę, rozszerzymy standardową dyrektywę n g -m o d e l oraz dokonam y integracji z zewnętrznymi widżetami wejściowymi. Chodzi nam o to, że dyrektywa n g -m o d e l jest dobra w tym, co robi, czyli dwustronnym wiązaniu danych. Jeżeli do naszej aplikacji wprowadzi­ m y nowy widżet wejściowy, to chcem y, aby zachowywał się w taki sam sposób ja k ta dyrektywa, tzn. dodajem y go do kodu H TM L, deklarujem y dyrektywę n g -m o d e l dla niego i gotowe. Im p lem en tację opisanego rozw iązania przedstaw im y na przykładzie in teg ra cji z naszą apli­ k acją zewnętrznego suwaka. Będzie to zbudowany na bazie biblioteki jQ u ery widżet n o U i S l i d e r (http://refreshless.com /nouislider/), który zapakujemy do dyrektywy wielokrotnego użytku. Zacznie­ m y od pliku index.htm l, w którym zobaczysz, jak planujemy używać naszej dyrektywy: < t i t l e > A p l i k a c j a z su w a k ie m < / title > < lin k r e l= "s t y le s h e e t " h r e f = "j q u e r y .n o u is lid e r . c s s "> < s t y l e t y p e = "t e x t / c s s "> . s lid e r { d is p la y : b lo c k ; h e ig h t : 20px; m a rgin : 20px; } < / s t y le > < d iv n g - c o n t r o ll e r = " M a in C t r l < d iv > A k t u a ln a w a rto ść suwaka:

as m a in C t r l" >

{ {m a in C t r l. s e le c t e d V a lu e } }

< n o - u i- s li der c l a s s = " s l id e r " n g - m o d e l= "m a in C tr l. s e le c t e d V a lu e " ra n g e -m in = "5 0 0 " ra n g e -m a x = "5 0 0 0 "> < / n o - u i- s lid e r > < d iv > < in p u t type ="n u m b e r" ng-m odel="m ai n C t r l . t e x t V a lu e " m in = "5 0 0 " m ax="5000" p la c e h o ld e r= "U s ta w w a r to ść "> Ustaw w a rto ść suwaka

228

|

Rozdział 13. Zaawansowane opcje definicji dyrektyw

< s c r i pt < s c r ip t < s c r i pt < s c r ip t < s c r ip t


s r c = " h t t p : / / c o d e . jq u e r y . c o m / j q u e r y - 1 . 1 1 . 1 . js "> < / s c r i p t > s r c = "j q u e r y . n o u i s l i d e r . m in . j s " > < / s c r i pt> s r c = " h t t p s :/ / a j a x . g o o g le a p is . c o m / a ja x / li b s / a n g u la r j s / 1 . 3 . 1 1 / a n g u l a r . j s " > < / s c r i pt> s r c = " a p p . j s " > < / s c r i pt> s r c = " n o u i- s li d e r . js "> < / s c r ip t >

Kod HTM L naszej aplikacji jest nieskomplikowany. W nagłówku dołączyliśmy plik CSS dla n o U iS l i d e r , a na końcu najpierw dołączyliśmy bibliotekę jQ uery, po niej plik jqu ery.n ou islider.m in .js, a następ­ nie bibliotekę AngularJS i standardowe zależności. W elem encie < d i v > kontrolującym aplikację najpierw wyświetlamy bieżącą w artość zm iennej s e l e c t e d V a l ue

z kontrolera, a później wiążemy dyrektywę n o - u i - s l i d e r z tą zm ienną za pom ocą

dyrektywy n g - m o d e l . D odatkow o zdefiniow aliśm y przedział d ozw olonych w artości suwaka za pom ocą atrybutów r a n g e - m in i r a n g e - m a x . Dalej znajdują się sekcja zawierająca pole tekstowe powiązane z inną zm ienną ( t e x t V a l u e ) i przy­ cisk. K liknięcie tego przycisku pow inno spow odow ać ustaw ienie suw aka n a w artość wpisaną w polu tekstowym. Teraz spójrzm y na kontroler tej aplikacji: / / Plik: r13/directive-slider/app.js a n g u la r . m o d u le ( 's li d e r A p p ', []) . c o n t r o l l e r ( 'M a i n C t r l ', [ f u n c t io n ( ) var s e lf = t h is ;

{

s e lf . s e l e c t e d V a l u e = 2000; s e l f . t e x t V a l u e = 4000; s e lf . s e t S e le c t e d V a lu e = f u n c t io n ( ) { s e lf . s e l e c t e d V a l u e = s e lf . t e x t V a l u e ; }; }]);

K o n troler tej aplikacji je s t niew ielki. Zaw iera tylko dwie zm ien ne m odelow e ( s e l e c t e d V a l u e i t e x t V a l u e ) oraz fu n k cję p o b ierającą w artość zm ien nej t e x t V a l ue i zapisującą ją w zm ien nej s e le c t e d V a lu e .

Celem tej funkcji jest pobranie wartości z pola tekstowego i ustawienie je j w su­

waku. O bie zm ienne m ają ustaw ioną w artość początkow ą, dzięki czem u in terfejs użytkownika jest nieco urozmaicony. Teraz m ożem y w końcu przyjrzeć się dyrektywie n o U i S l i d e r : / / Plik: r13/directive-slider/noui-slider.js a n g u la r . m o d u le ( 's lid e r A p p ') . d i r e c t i v e ( 'n o U i S l i d e r ', [ f u n c t io n ( ) { retu rn { r e s t r ic t : 'E ', r e q u ir e : 'n g M o d e l', l i n k : f u n c t io n ( $ s c o p e , $elem ent, $ a t t r , n g M o d e lC trl)

{

$ e le m e n t .n o U iS lid e r ( {

/ / w kontrolerze ngModelCtrl m oże jeszcze nie być wartości początkow ej sta rt:

0,

Kontrolery dyrektyw i funkcja require

| 229

ran ge :

{

/ / $attr domyślnie dostarcza wartości łańcuchowych / / nouiSlider działa na liczbach, dlatego konieczna jest konwersja m in: N u m b e r($ a ttr.ra n g e M in ), max: N u m b e r($ attr.ran ge M ax) } });

// Gdy w AngularJS zm ienią się dane, należy poinform ow ać o tej zm ianie dyrektywę zewnętrzną. n g M o d e lC tr l.$ r e n d e r = f u n c t io n ( ) { $ e le m e n t .v a l(n g M o d e lC t r l.$ v i ew Value); };

// Jeśli dane zm ienią się p oza AngularJS, $ e le m e n t . o n ( 's e t ',

f u n c t io n ( a r g s )

{

/ / to należy poinform ow ać także AngularJS, że trzeba zaktualizować interfejs. $ s c o p e . $ a p p ly ( f u n c t io n ( )

{

/ / ustawienie danych w AngularJS n g M o d e lC t r l. $ s e t V ie w V a lu e ( $ e le m e n t.v a l( ) ) ; }); }); } }; }]);

Jak jest zbudowana ta dyrektywa? Przeanalizujem y ją krok po kroku: 1. Utworzyliśmy dyrektywę elementową wymagającą tego, aby w tym samym elem encie, w k tó­ rym zostanie użyta, była też zdefiniowana dyrektywa n g M o d e l . 2. W funkcji l i n k najpierw tworzymy n o U iS l i d e r przez wywołanie konstruktora z odpowiednimi parametrami. Wykorzystujemy atrybuty z kodu HTML, które konwertujemy z łańcuchów na liczby. 3. Jako że n o U i S l i d e r jest wtyczką do jQ uery, a w pliku in d ex .h tm l bibliotekę tę ładujem y przed AngularJS, bezpośrednio wywołujemy funkcję n o U i S l i d e r na naszym elem encie, ponieważ biblioteka jQ uery idealnie integruje się z AngularJS. 4. Następnie, aby sfinalizować integrację dyrektywy ngM o d e l z naszym zewnętrznym składnikiem, m usim y wykonać dwie czynności: a. Gdy nastąpi zm iana danych w AngularJS, m usim y zaktualizować zewnętrzny kom ponent interfejsu użytkownika. W tym celu przesłaniam y m etodę $ r e n d e r kontrolera n g M o d e lC t r l i ustawiamy w niej wartość w zewnętrznym kom ponencie. N ajnowsza wartość ustawiona w zmiennej, do której odwołuje się n g M o d e l , jest dostępna w kontrolerze ngM odel C t r l , w zmien­ nej $ v ie w V a l u e . System AngularJS wywołuje m etodę $ r e n d e r za każdym razem, gdy zmieni się w nim wartość m odelu (np. kiedy zostanie zainicjow ana w naszym kontrolerze). b. Gdy dane zm ienią się poza system em AngularJS, m usim y go o tej zmianie poinform ować. W tym celu wywołujemy funkcję $ s e t V ie w V a l u e na n g M o d e l C t r l z najnowszą wartością. 5. Ponadto, ja k wspom inaliśm y wcześniej, system AngularJS aktualizuje interfejs użytkownika zawsze, gdy dowie się o zmianach w kontrolerze. Zewnętrzny komponent interfejsu użytkownika nie podlega cyklowi życia AngularJS, więc musimy ręcznie wywoływać funkcję $ s c o p e . $ a p p l y ( ) , aby wymusić aktualizację. Funkcja ta przyjmuje jako argum ent opcjonalną funkcję i powo­ duje uruchom ienie cyklu obliczeniowego AngularJS odpowiedzialnego za aktualizację inter­ fejsu użytkownika o najnowsze wartości.

230

|

Rozdział 13. Zaawansowane opcje definicji dyrektyw

Podczas integrowania z aplikacją zewnętrznych kom ponentów interfejsu użytkownika umożli­ wiających wprowadzanie danych zawsze dobrym pomysłem jest wykorzystanie dyrektywy ngModel , dzięki czemu kom ponent ten będzie działał jak każdy inny widżet wejściowy. Jeśli skorzystasz z tej rady, musisz też zająć się dwustronnym wiązaniem danych: • Gdy dane w systemie AngularJS ulegną zm ianie, m usim y odpowiednio zaktualizować k om ­ ponent zewnętrzny (przez przesłonięcie funkcji ngM odelCtrl.$render). • Kiedy dane zm ienią się poza system em AngularJS (przez zdarzenie spoza tego systemu), n a ­ leży tę zm ianę przechw ycić i na je j podstawie zaktualizow ać m odel AngularJS (wywołując funkcję ngModelCtrl.$setViewValue z zaktualizowaną wartością). Jako że procedury nasłuchowe są dodawane tylko do elem entu, usunięcie tego elementu oznacza także skasowanie tych procedur. Jeśli dodamy kolejną procedurę nasłuchową, to nią również m u­ sim y się później zająć.

Tworzenie walidatorów W iesz już, do czego służą kontrolery dyrektyw i ja k przy użyciu dyrektywy ngModel tworzyć wła­ sne dyrektywy wejściowe, więc możesz przystąpić do tworzenia własnego walidatora. Jak pokaza­ liśmy w rozdziale 2., system AngularJS zawiera wiele wbudowanych dyrektyw do sprawdzania po­ prawności danych wprowadzonych w form ularzach, np. required, ng-required, ng-minlength itd. Jest to solidny zestaw narzędzi, których m ożna używać w projektach. Ale czasami pewne konkretne czynności weryfikacyjne wykonuje się wiele razy w różnych sytu­ acjach. W takim przypadku lepiej jest utworzyć własne walidatory, niż stosować standardowe walidatory AngularJS. Przeanalizujem y trochę wydumany przykład aplikacji, w której chcem y sprawdzić, czy użytkow­ nik wpisał w polu tekstowym prawidłowy kod pocztowy w form acie amerykańskim. W USA kod pocztowy występuje w jednym z trzech następujących formatów: • 12345, • 12345 1234, • 12345-1234. N ajpierw zobaczm y plik in d ex .h tm l, dem onstrujący sposób użycia naszej dyrektywy walidacyjnej: < t i t l e > A p l i k a c j a g ie id o w a < / t it le > < sty le > i n p u t . n g - i n v a l id { b a c k g ro u n d : p in k ; } < / s t y le > < d iv n g - c o n t r o ll e r = " M a in C t r l

as m a in C t r l" >

Kontrolery dyrektyw i funkcja require

| 231

W pisyw anie kodu pocztowego

Kod pocztow y może być w p isa n y w jednym z t rz e c h form atów :
< u l> < l i > 1 2 3 4 5 < / li > < l i >12345 1 2 3 4 < / li > < l i > 1 2 3 4 5 -1 2 3 4 < / li> < / u l>
W pisz praw idło w y kod pocztowy: < in p u t t y p e = "t e x t " nam e="zi pFi e ld " ng-m odel="m ai n C t r l . z i p " val i d - z i p> < d iv n g - s h o w = " z ip F o r m . z ip F ie ld . $ e r r o r . z ip "> W p is a n o n ie p ra w id ło w y kod poczto w y< /div>
< s c r ip t s r c = " h t t p s : / / a j a x . g o o g le a p i s .c o m / a ja x / li b s / a n g u la r j s / 1 . 3 . 1 1 / a n g u la r . j s " > < / s c r ip t > < s c r i pt s r c = " a p p . j s " > < / s c r i pt> < s c r i pt s r c = " d i r e c t i v e . j s " > < / s c r i pt>


W kodzie tym interesuje nas tylko form ularz zawierający pole wejściowe z dyrektywą n g - m o d e l . C hcem y m ieć pew ność, że użytkownik popraw nie wpisze kod pocztowy, i dlatego do pola tego dodaliśmy dyrektywę walidacyjną v a l i d - z i p . Zaraz za tym polem zdefiniowaliśmy elem ent < d i v > zawierający inform ację o błędzie, która zostanie wyświetlona, gdy użytkownik wpisze niepraw i­ dłowy tekst. Zawartość pliku ap p .js jest banalnie prosta i wygląda tak: // Plik: r13/directive-custom-validator/app.js angul a r . m o d u le ( 's t o c k M a r k e t A p p ',

[])

. c o n t r o l l e r ( 'M a i n C t r l ', [ f u n c t io n ( ) t h is .z ip = '';

{

}]);

Jest to definicja kontrolera, w którym pole z i p konstruktora zostało domyślnie ustawione na war­ tość pustą. Na koniec spójrzmy jeszcze na dyrektywę reprezentującą nasz walidator: // Plik: r13/directive-custom-validator/directive.js a n g u la r . m o d u le ( 's t o c k M a r k e t A p p ') . d i r e c t i v e ( 'v a l i d Z i p ', [ f u n c t io n ( ) { v a r zipC odeR egex = / ^ \ d { 5 ] ( ? : [ - \ s ] \ d { 4 ) ) ? $ / g ; retu rn { r e s t r i c t : 'A ', r e q u ir e : 'n g M o d e l', l i n k : f u n c t io n ( $ s c o p e , $elem ent, $ a t t r s , n g M o d e lC trl) n g M o d e lC t r l. $ v a lid a t o r s . z ip = f u n c t io n ( v a lu e ) { r e t u r n z ip C o d e R e g e x . t e s t ( v a lu e ) ; }; } }; }]);

232

|

Rozdział 13. Zaawansowane opcje definicji dyrektyw

{

Dyrektywa validZ ip definiuje wyrażenie regularne służące do weryfikacji, czy dany łańcuch re­ prezentuje prawidłowy kod pocztowy w formacie używanym w USA. W alidator ten zależy od kon­ trolera dyrektywy ngModel, który dołącza w swojej definicji. Na listingu widoczna jest też funkcja lin k , która zawiera logikę naszej dyrektywy. W systemie AngularJS 1.3 ułatwiono dodawanie własnych walidatorów do łańcucha walidatorów AngularJS. K ontroler ngM odelController udostępnia klucz $ v a lid a to rs . Każda dodana do niego funkcja zostanie wykonana przez AngularJS w celu sprawdzenia poprawności określonego pola, do którego została dodana dyrektywa walidacyjna. W tym przypadku do wspomnianego k o n tro ­ lera dodaliśmy walidator zip, który pobiera bieżącą wartość z interfejsu użytkownika i zwraca wartość tru e, jeśli stwierdzi, że jest ona poprawna według wyrażenia regularnego, lub fa ls e w prze­ ciwnym wypadku. W efekcie klucz fo rm F ie ld .$ e rro r.z ip zostanie ustawiony na tru e albo fa ls e . W arto też zauważyć, że nazwa dyrektywy nie m a nic wspólnego z ostatecznym kluczem w obiek­ cie $e rro r. W obiekcie tym używany jest klucz zastosowany do dodania dyrektywy do listy walidatorów $ v a lid a to rs. Gdybyśmy chcieli wykonywać walidację asynchronicznie (np. sprawdzać dostępność nazwy użyt­ kow nika), to moglibyśm y dodać nasz walidator do klucza $asyncV alidators kontrolera ngModel ^ C o n tro ll er. Ale w przypadku walidacji asynchronicznej zamiast logicznej wartości tru e lub fa ls e należy zwrócić obietnicę AngularJS. Jeśli obietnica zostanie spełniona, pole zostaje uznane za prawidłowo wypełnione, a w przeciwnym razie następuje wyświetlenie inform acji o błędzie. N a­ leży też wiedzieć, że walidatory asynchroniczne są wywoływane dopiero po wszystkich walidatorach synchronicznych dla danego pola i pod warunkiem , że wszystkie one zwrócą wartość tru e. Aby m ożna było wyświetlić jakiś wskaźnik ładowania, podczas gdy wykonywana jest asynchro­ niczna operacja walidacji, kontrolka form ularza udostępnia zm ienną $pending. Gdyby więc nasza dyrektywa v alid Z ip przeprow adzała w alidację asy n ch ro n iczn ie, to m oglibyśm y spraw dzać zm ienną zipForm .zipField.$pending — jeśli m iałaby ona wartość tru e, m oglibyśmy wyświetlać obracającą się ikonkę, by poinform ow ać użytkownika, że cały czas coś się dzieje. Krótko mówiąc, wachlarz zastosowań walidatorów ogranicza tylko Tw oja wyobraźnia.

Kompilacja W swoim cyklu życia dyrektywa przechodzi przez dwie fazy: kom pilacji (compile) i łączenia (lin k ). Etap łączenia ju ż zgłębiliśmy szczegółowo, więc czas na dokładniejsze poznanie kroku kom pilacji. Zanim system dojdzie do etapu lin k dyrektywy, je j kod H TM L jest ju ż przetworzony i wszystkie potrzebne dyrektywy są pobrane przez kompilator AngularJS oraz powiązane z bieżącym zakresem. Jeżeli w tym m om encie dynamicznie dodamy do kodu H TM L jakąś dyrektywę albo dokonamy poważnej ingerencji w strukturę D O M , w ram ach której nastąpi integracja z istniejącym i dyrek­ tywami AngularJS, to dyrektywy te nie zadziałają prawidłowo. O d pow iednim m iejscem do szperania w kodzie H T M L szablonu i p rzekształcania struktury D O M jest krok compile w dyrektywie. N igdy n ie u żyw a się fu n k c ji lin k i compile razem , ponieważ jeśli użyje się klucza compil e, należy zw rócić z niego fu nkcję łączącą. Zobaczm y, ja k to wygląda w praktyce, na przykładzie dyrektywy o nazwie form-element:

Kompilacja

| 233

< t i t l e > A p l i k a c j a z dynamicznym fo rm u la rz e m < / ti t le > < d iv n g - c o n t r o ll e r = " M a in C t r l as m a in C t r l" >
< v a li d a t io n k e y = "r e q u ir e d "> W pisz nazwę u żytk o w n ika < / v a li d a ti on> < v a li d a t io n key="m i n le n g t h "> Nazwa u ż y tk o w n ika musi za w ie ra ć p rz y n a jm n ie j p ię ć l i t e r . < / v a li d a ti on>
Nazwa u żytk o w n ika:

{{m a in C tr l.u se r n a m e }}

< v a li d a t io n k e y = "r e q u ir e d "> W pisz h a s ło < / v a li d a ti on> < v a li d a t io n k e y = "p a t t e r n "> H a sło może za w ie ra ć t y lk o c y f r y i l i t e r y . < / v a li d a ti on>
H a sło : { {m a in C t r l. p a s s w o r d } } < b u tt o n > W y ślij< / b u t to n > < s c r i pt s r c = " h t t p s :/ / a j a x . g o o g le a p i s . co m /a jax /li b s / a n g u la r j s / 1 . 3 . 1 1 / a n g u l a r . j s " > < / s c r i pt> < s c r i p t s r c = " a p p . j s " > < / s c r i pt> < s c r i p t s r c = " d i r e c t i v e . j s " > < / s c r i pt>

W kodzie tym zdefiniowaliśmy form ularz zawierający kilka etykiet i pól wejściowych. Każde pole ma własną logikę walidacji i własną wiadomość o błędzie. Zamiast pisać skomplikowane struktury H TM L przy użyciu technik opisanych w rozdziale 4., wykorzystamy dyrektywę f o r m - e l em ent , w której zdefiniujemy reguły walidacji i wiązania danych. Dyrektywa ta umożliwia również definiowanie po­ wiadomień o błędach, które m ożna automatycznie wyświetlać w odpowiednich sytuacjach, zależ­ nie od warunków. Jedynym wymogiem tej dyrektywy jest to, że m usi być używana w formularzu.

234

|

Rozdział 13. Zaawansowane opcje definicji dyrektyw

O to lista składników obsługiwanych przez naszą dyrektywę form-element: • pola wejściowe różnego typu (te x t, password itd.); • nazwa pola wejściowego, aby m ożna było znaleźć błędy; • zm ienna ngModel do związania; • etykieta do pokazania obok pola formularza; • każdy m echanizm walidacji oparty na ngModel, np. required, ngMinlength, ngPattern itd. K ontroler jest bardzo prosty: / / Plik: r13/directive-compile/app.js a n g u la r .m o d u le ('d y n a m ic F o rm A p p ', [ ]) . c o n t r o l l e r ( 'M a i n C t r l ', [ f u n c t io n ( ) var s e lf = t h is ;

{

s e lf.u se r n a m e = ' ' ; s e lf . p a s s w o r d = ' ' ; }]);

Kontroler ten definiuje tylko kilka zm iennych do wiązania w kodzie H TM L. Nie im plem entujem y funkcji onClick dla formularza, ponieważ nie interesują nas takie zdarzenia. Zadaniem naszej dyrektywy jest przeanalizowanie kodu H TM L i wykonanie następujących czynności: • wygenerowanie odpowiedniego znacznika wejściowego z odpowiednią dyrektywą ng-model i regułam i w alidacji; • wygenerowanie szablonu dla wszystkich wiadomości o błędach i zadbanie o to, by były wy­ świetlane w odpowiednich sytuacjach; • zignorowanie wszystkich atrybutów, które są nieznane lub nieobsługiwane przez dyrektywę; • dodanie do zakresu funkcji wyświetlających wiadomości o błędach. Zobaczm y teraz, ja k wygląda kod źródłowy naszej dyrektywy: / / Plik: r13/directive-compile/directive.js a n g u la r.m o d u le ('d y n a m ic F o rm A p p ') . d ir e c t iv e ( 'f o r m E le m e n t ', [ f u n c t io n ( )

{

retu rn { r e s t r ic t : 'E ', r e q u ir e : '^ f o r m ', sco pe : tru e , co m p ile : fu n c t io n ($ e le m e n t, $ a t t r s ) v a r e x p e c t e d In p u t A t t rs = { 'r e q u i r e d ': 'r e q u i r e d ', 'n g - m in le n g t h ': 'n g M in l e n g t h ', 'n g - p a t t e r n ': 'n g P a t t e r n '

{

// dalsze instrukcje };

/ / rozpoczęcie pobieran ia treści z kodu HTML v a r v a lid a t io n K e y s = $ e l e m e n t . f i n d ( 'v a l i d a t i o n ') ; v a r p r e s e n t V a lid a t io n K e y s = { } ; v a r inputName = $ a ttrs.n a m e ;

Kompilacja

| 235

a n g u la r . f o r E a c h ( v a lid a t io n K e y s , f u n c t io n ( v a lid a t io n K e y ) v a lid a t io n K e y = a n g u la r . e le m e n t ( v a lid a t io n K e y ) ; p r e s e n t V a li d a t io n K e y s [ v a li d a t i o n K e y . a t t r ( 'k e y ')] = v a l i d a ti o n K e y . t e x t ( ) ;

{

});

// rozpoczęcie generow ania finalnego elementu HTML v a r elementHtml = '< d i v > ' + '< l a b e l > ' + $ a t t r s . l a b e l + '< / l a b e l > '; elementHtml += '< in p u t t y p e = " ' + $ a t t r s . t y p e + ' " nam e="' + inputName + ' " n g -m o d e l= "' + $ a t t r s . b in d T o + ' " ' ; $ e le m e n t .r e m o v e A t t r ( 't y p e ') ; $ e le m e n t.re m o v e A ttr('n a m e ') ; f o r (v a r i in e x p e c te d ln p u t A tt rs ) { i f ( $ a t t r s [ e x p e c t e d In p u t A t t r s [ i ] ] !== u n d e fin e d ) elementHtml += ' ' + i + ' = " ' + $ a t t r s [ e x p e c t e d In p u t A t t r s [ i] ] + ' " ' ;

{

} $ e le m e n t .r e m o v e A t t r ( i) ; } elementHtml += ' > ' ; elementHtml '< sp a n ' '

+= n g - r e p e a t = "( k e y , te x t) in v a l i d a t o r s " n g -sh o w = "h a sE rro r(k e y )"' + n g - b i n d = "t e x t " > < / s p a n > ';

' +

elementHtml += '< / d i v > '; $ e l e m e n t.h tm l(elem entH tm l); r e t u r n f u n c t io n ( $ s c o p e , $elem ent, $ a t t r s , fo rm C t rl) { $ s c o p e . v a lid a t o r s = a n g u la r . c o p y ( p r e s e n t V a lid a t io n K e y s ) ; $ s c o p e . h a s E r r o r = f u n c t io n ( k e y ) { r e t u r n !! fo rm C trl [in p u t N a m e ][' $ e r r o r '] [k e y ]; }; }; } }; }]);

Oto najważniejsze cechy dyrektywy fo r m E le m e n t , której definicja znajduje się powyżej: 1. D odaliśm y kucz r e q u i r e , oznaczający, że dyrektywa m a być używana wewnątrz formularzy, oraz nadaliśm y je j nowy zakres podrzędny, aby dodane do niej funkcje nie nadpisały żadnych globalnych zm iennych lub funkcji. 2. Zdefiniowaliśm y też funkcję c o m p i le , wywoływaną z elem entem i atrybutami. Funkcja ta jest wykonywana, zanim zakres staje się dostępny, więc nie wstrzykujemy go do niej. 3. Zaczynamy pobierać i przetwarzać istniejący znacznik fo r m - e le m e n t z kodu H TM L oraz po­ bieram y reguły walidacji, wiadomości i istniejące atrybuty, które nas interesują. 4. Później rozpoczynam y generowanie nowego kodu H TM L dla dyrektywy. Jako że będziemy dynamicznie dodawać dyrektywy AngularJS, robim y to na etapie kom pilacji. Gdybyśmy za­ m iast tego zrobili to na etapie łączenia, system AngularJS nie wykryłby tych dyrektyw i apli­ kacja by nie działała.

236

|

Rozdział 13. Zaawansowane opcje definicji dyrektyw

5. Dodajem y znacznik wejściowy o nazwie ng-model i wszystkie reguły walidacji, które znaleźliśmy w kodzie H TM L. 6. Następnie zastępujem y istniejącą treść dyrektywy nowo wygenerowaną treścią. 7. Na koniec zwracamy funkcję postLink (nie m ożem y użyć słowa kluczowego lin k razem z compile, więc musimy zwrócić funkcję łączącą z etapu kompilacji), która dodaje tablicę val idators i funk­ cję hasError, służące do wyświetlania wiadomości o błędach w odpowiednich m om entach. Użyliśm y do tego kontrolera form, który został dołączony przez dyrektywę zgodnie ze stan­ dardami opisanymi w rozdziale 4.

Funkcje pomocnicze AngularJS Pewnie zauważyłeś, że w funkcji compile dyrektywy formElement wywołaliśmy funkcję o nazwie angular.forEach. System AngularJS udostępnia kilka globalnych funkcji pomocniczych pozwa­ lających wykonywać czynności, których nie da się wygodnie wykonać przy użyciu zwykłego in­ terfejsu API JavaScriptu. Oto lista niektórych z tych funkcji: angular.forEach Iterator do przeglądania obiektów i tablic pomocny przy pisaniu kodu w stylu funkcyjnym. angular.fromJson i angular.toJson Metody pomocnicze służące do konwersji łańcuchów na obiekty JSON i odwrotnie. angular.copy Funkcja wykonująca głęboką kopię danego obiektu i zwracająca nowo utworzony obiekt. angular.equals Funkcja sprawdzająca, czy dwa obiekty, wyrażenia regularne albo dwie tablice lub wartości są równe. Dla obiektów i tablic wykonuje porównywanie głębokie. an gu lar.isO bject, angular.isArray i angular.isFunction Metody pomocnicze do szybkiego sprawdzania, czy zmienna jest obiektem, tablicą bądź funkcją. an gu lar.isString,angular.isN um ber i angular.isD ate Metody pomocnicze do sprawdzania, czy zmienna jest łańcuchem, liczbą lub obiektem daty. Funkcji pomocniczych jest o wiele więcej. Wszystkie je można znaleźć w oficjalnej dokumentacji systemu AngularJS (https://docs.angularjs.org/api/ng/function).

Gdy uruchom isz opisywaną aplikację w przeglądarce, n a stronie ukaże się form ularz z dwoma polam i wejściowymi i inform acjam i o błędach dotyczącymi wymaganych pól. Podczas wpisywa­ nia tekstu zauważysz, że w iadom ość najpierw zm ieni się na inną, a potem form ularz zostanie uznany za wypełniony prawidłowo. Jak napisaliśm y w cześniej, fu nkcji compile używa się niezwykle rzadko, gdy trzeba wykonywać poważne operacje na strukturze D O M podczas działania aplikacji. W większości przypadków p o­ dobny efekt m ożna osiągnąć dzięki zastosowaniu transkluzji albo funkcji lin k . M im o to funkcja compile zapewnia dodatkowe m ożliwości na wypadek, gdyby były potrzebne.

Kompilacja

| 237

Łączenie początkowe i końcowe Funkcja link, w formie, w jakiej najczęściej ją piszemy (i zwracamy z funkcji compile), to tzw. funkcja łączenia końcowego. Podczas jej wykonywania wszystkie dyrektywy potomne są już skompilowane i odpowiednio połączone. Przekształcenia struktury DOM (nie dodawanie dy­ rektyw AngularJS, ale np. tworzenie wykresów) są w tym momencie bezpieczne. Ale gdybyśmy potrzebowali punktu zaczepienia, aby wykonać coś przed połączeniem potomków, możemy użyć tzw. funkcji łączenia początkowego. W momencie jej wykonania dyrektywy po­ tomne nie są jeszcze połączone, a przekształcenia struktury DOM nie są bezpieczne i mogą mieć dziwne skutki. Funkcje łączenia początkowego i końcowego można zdefiniować przez użycie obiektu zamiast funkcji link. Innymi słowy, zamiast pisać: j lin k :

f u n c t io n ( $ s c o p e , $elem ent, $ a t t r s )

(}

}

należy napisać: j lin k : j pre : f u n c t io n ( $ s c o p e , $elem ent, $ a t t r s ) p o s t: f u n c t io n ( $ s c o p e , $elem ent, $ a t t r s )

(}, (}

} }

To samo dotyczy zwrotu wartości z funkcji kompilującej, tzn. zamiast zwracać funkcję o nazwie p o st-lin k , można zwrócić obiekt z kluczami pre i post.

Priorytet i terminal Pozostały ju ż tylko dwie opcje tworzenia dyrektyw — p r i o r i t y i t e r m i n a l . O pcja p r i o r i t y służy do określania kolejności ewaluacji dyrektyw, jeśli jest ich kilka w jednym elemencie. Na przykład jeżeli w elemencie zadeklarujemy dyrektywy n g M o d e l , n g P a t t e r n i n g M i n l e n g t h , to m usim y za­ dbać, aby jako pierwsza została wykonana dyrektywa n g M o d e l . A zatem możemy nadać dyrektywie n g P a tte rn

niższy priorytet niż dyrektywie n g M o d e l .

Dom yślnie wszystkie tworzone przez nas dyrektywy m ają priorytet 0 . Im wyższa ta liczba, tym wyższy priorytet, a dyrektywy o wyższym priorytecie są kompilowane i łączone przed tymi o niż­ szym priorytecie. W definicji dyrektywy m ożna też używać słowa kluczowego t e r m i n a l , które służy do określania ostatniego priorytetu dyrektyw do wykonania. Ponadto ustawienie tej opcji na t r u e powoduje, że dyrektywy potom ne nie zostaną tknięte ani skompilowane. Dom yślnie opcja ta jest ustawiona na fa lse

. W szystkie dyrektywy w elemencie m ające taki sam priorytet zostaną wykonane, ponieważ

kolejność wykonywania dyrektyw o tym samym priorytecie jest niezdefiniowana.

2B8

I

Rozdział 1B. Zaawansowane opcje definicji dyrektyw

Integracja zewnętrzna O pisaliśm y ju ż wszystkie ważne składniki obiektu definicji dyrektywy oraz zaprezentow aliśm y kilka przykładów budowy skomplikowanych dyrektyw. W tym podrozdziale przedstawiamy ty­ pową procedurę integracji z aplikacji zewnętrznych dyrektyw wizualnych, np. wykresów i innych składników nastaw ionych nie na pobieranie danych, tylko na ich prezentowanie. Jeśli chodzi o proste wyświetlanie danych w formacie H TM L (jak w naszej dyrektywie s t o c k W id g e t ), to wystarczy użyć dyrektyw do wiązania danych systemu AngularJS. Ale w przypadku zewnętrznych kom ponentów wiązaniem danych należy zająć się samodzielnie. Jako twórcy dyrektyw wizualnych m am y do wykonania kilka czynności: 1. Przed uruchom ieniem dyrektywy m usim y poczekać na załadowanie odpowiedniej biblioteki. 2. M usim y pobrać dane z kontrolera i przekształcić je na form at odpowiedni dla naszego ze­ wnętrznego kom ponentu. 3. W yświetlamy dane za pom ocą zewnętrznego kom ponentu. 4. Gdy w AngularJS zm ienią się dane, aktualizujem y zewnętrzny kom ponent. 5. M usim y nasłuchiwać zdarzeń z zewnętrznego kom ponentu i przekazywać je do odpowied­ niego kontrolera przez wiązania funkcji. Teraz w ram ach przykładu zintegrujem y z własną aplikacją kom ponent Google Charts, który asynchronicznie ładuje swój interfejs API, i utworzymy dyrektywę p i e C h a r t , aby m óc wygodnie korzystać z tego kom ponentu. W iększość dyrektyw jest m niej skomplikowana, ale poniższy przy­ kład m a służyć jako szkielet do integracji z aplikacją każdego rodzaju dyrektywy wizualnej, jakiego możesz potrzebować. Jak zawsze zaczniemy od pliku in d ex .h tm l, aby zobaczyć, w jaki sposób chcemy używać planowanej dyrektywy: < h tm l> < t i t l e > A p l i k a c j a z w ykresam i G o o g le < / ti t le > < d iv n g - c o n t r o ll e r = " M a in C t r l as m a in C t r l" > < d iv > < b u tto n n g - c l i c k = "m a in C t r l. c h a n g e D a t a ( ) "> Zmień dane w ykresu < / b u tto n > < /d i v> < d iv p ie - c h a r t c h a rt - d a t a = "m a in C t r l .p ie C h a r tD a t a " c h a r t - c o n f ig = "m a in C t r l . p ie C h a r t C o n f ig "> < /d i v> < / d iv >

Integracja zewnętrzna

| 239

< s c r i p t s r c = " h t t p : / / w w w .g o o g le .c o m / jsa p i"> < / sc r i pt> < s c r i p t s r c = " h t t p s :/ / a j a x . g o o g le a p is . c o m / a ja x / li b s / a n g u la r j s / 1 . 3 . 1 1 / a n g u l a r . j s " > < / s c r i p t > < s c r i p t s r c = " a p p . j s " > < / s c r i pt> < s c r i p t s r c = " g o o g le C h a r t L o a d e r . j s " > < / s c r ip t > < s c r ip t s r c = "p i e C h a r t.js "> < / s c rip t>

Kod H TM L, w którym zastosowano dyrektywę pieC hart, jest bardzo prosty. Dyrektywy tej uży­ wamy w form ie atrybutu oraz przekazujemy do niej inform acje konfiguracyjne i potrzebne do wyświetlenia wykresu za pom ocą argumentów. Dzięki tem u dyrektywa nie jest związana na stałe z jed ną usługą lub jednym obiektem konfiguracji i m ożna używać je j wielokrotnie. Utworzyliśmy też przycisk służący do zm ieniania danych na wykresie. Jeśli chodzi o zależności skryptowe, to najpierw ładujem y asynchroniczny m echanizm wczytywa­ nia interfejsów A PI Google (Google Loader) (h ttp ://w w w .g oog le.com /jsapi) u łatwiający ładowanie interfejsów A PI, w tym przypadku Google Charts. Pokażm y również, ja k wykorzystać obietnice AngularJS do poczekania na załadowanie tego A PI przed rozpoczęciem rysowania wykresów. Teraz przyjrzymy się głównemu kontrolerow i aplikacji, który zawiera definicje danych i konfigu­ rację wykresu: // Plik: r13/directive-google-chart/app.js angul a r . m o d u le ( 'g o o g l e C h a rtA p p ', []) . c o n t r o l l e r ( 'M a i n C t r l ', [ f u n c t io n ( )

{

v a r s e lf = t h is ; s e lf . p ie C h a r t D a t a = [ { la b e l: { la b e l: { la b e l:

'P i e r w s z y ', v a lu e : 2 5 }, 'D r u g i ', v a lu e : 5 4 }, 'T r z e c i ', v a lu e : 75}

]; s e lf . p ie C h a r t C o n f ig = { t i t l e : 'W ykre s r a z , dwa, t r z y ', firstC o lu m n H e a d e r: 'L i c z n i k ', secondColum nHeader: 'R z e c z y w is t a w a r to ś ć ' }; s e lf . c h a n g e D a t a = f u n c t io n ( )

{

s e l f . p ie C h a r t D a t a [ 1 ] . v a l u e = 25; }; }]);

W pliku app.js zadeklarowaliśmy zm ienną pieChartData. Jest to tablica kluczy i wartości we wła­ snym form acie. W tym przykładzie treść ta je st wpisana na sztywno, ale m ogłaby być pobierana z serwera za pom ocą usługi $http. Ponadto w pliku tym zdefiniow aliśm y kilka ustawień k o n fi­ guracyjnych, np. nazwę wykresu i jego kolum n. Na końcu znajduje się prosta funkcja o nazwie changeD ata(), zm ieniająca wartość jednego elementu danych, aby m ożna było zobaczyć, czy m o ­ dyfikacja ta spowoduje autom atyczną zmianę wykresu. Następna część aplikacji to asynchroniczny mechanizm wczytywania, który zadba o to, by dyrekty­ wa pieChart nie rozpoczęła rysowania wykresu, zanim nie zostanie wczytany interfejs API:

240

|

Rozdział 13. Zaawansowane opcje definicji dyrektyw

/ / Plik: r13/directive-google-chart/googleChartLoader.js a n g u la r . m o d u le ( 'g o o g le C h a r t A p p ') . f a c t o r y ( 'g o o g le C h a r t L o a d e r P r o m is e ', [ '$ q ', '$ r o o t S c o p e ', '$ w in d o w ', f u n c t io n ( $ q , $ ro o tSc o p e , $window)

{

/ / utworzenie odroczonego obiektu v a r d e fe rre d = $ q . d e f e r ( ) ;

// asynchroniczne załadow anie API Google Charts $ w in d o w . g o o g le . lo a d ( 'v i s u a l i z a t i o n ',

'1 ',

{ p a ck age s:

[ 'c o r e c h a r t '] ,

c a llb a c k : f u n c t io n ( )

{

// Po załadow aniu API wywołujemy fu n kcję resolve wewnątrz fu n kcji $apply, / / poniew aż zdarzenie m a miejsce poza cyklem życia AngularJS. $ r o o t S c o p e . $ a p p ly ( f u n c t io n ( ) d e fe rre d .re so lv e ();

{

}); } });

/ / zwrot obiektu obietnicy do dołączenia do łańcucha przez dyrektywę r e t u r n d e fe rre d .p ro m is e ; }]);

Fabryka g o o g l e C h a r t L o a d e r P r o m i s e ładuje bibliotekę wizualizacyjną jeden raz i zwraca obietnicę, którą można dołączyć do łańcucha, aby dowiedzieć się, kiedy zakończy się ładowanie tej biblioteki. W tym celu użyto usługi AngularJS $ q (zobacz punkt „Usługa $q” w rozdziale 6., jeśli potrzebujesz odświeżenia w iadom ości), którą wykorzystaliśm y też w rozdziale 10. do obsługi fu nkcji r e s o l v e w trasie. Usługa $q umożliwia nie tylko odrzucenie obietnicy za pom ocą funkcji $ q . r e j e c t ( d a t a ) , lecz również tworzenie własnych obietnic, z której to m ożliwości korzystamy teraz. Tworzymy obiekt d e f e r r e d , reprezentujący asynchroniczne zadanie, które zostanie wykonane w przy­ szłości. Do jego utworzenia użyliśmy funkcji $ q . d e f e r ( ) . Później zwracamy obiekt d e f e r r e d . p r o m is e , do którego użytkownicy interfejsu A PI m ogą dodać wywołanie . t h e n ( ) , aby otrzymać powiado­ m ienie o wykonaniu (lub odrzuceniu) tego asynchronicznego zadania. Potem wywołujemy API Google z argum entam i odpowiednimi do załadowania biblioteki wizualizacyjnej i przekazujemy mu funkcję zwrotną, która m a zostać powiadom iona o zakończeniu zadania. W funkcji zwrotnej rozwiązujemy ( r e s o l v e ) utworzony przez nas odroczony obiekt, co powoduje wykonanie wszystkich bloków . t h e n . Ale ponieważ ta funkcja zwrotna jest wywoływana poza cy­ klem życia AngularJS, m usim y opakować ją w funkcję $ r o o t S c o p e . $ a p p l y , by system AngularJS wiedział, że m a ponow nie narysow ać interfejs użytkownika i w razie potrzeby przeprowadzić kom pletny cykl obliczeniowy. Na koniec m ożem y przyjrzeć się dyrektywie p i e C h a r t , która integruje się z usługą g o o g l e C h a r t s ^ L o a d e r P r o m is e

i Google Charts:

/ / Plik: r13/directive-google-chart/pieChart.js a n g u la r . m o d u le ( 'g o o g le C h a r t A p p ') . d i r e c t i v e ( 'p i e C h a r t ', [ 'g o o g le C h a r t L o a d e r P r o m is e ', f u n c t i o n (g o o g le C h a rtL o a d e rP ro m ise ) { v a r co n v e rtT o P ie C h a rtD a ta T a b le F o rm a t = fu n c tio n (firs tC o lu m n N a m e , secondColumnName, data)

{

Integracja zewnętrzna

| 241

v a r p ie C h a r t A r ra y = [[firstC o lu m n N a m e , secondColum nNam e]]; f o r (v a r i = 0; i < d a t a .le n g t h ; i+ + ) { p ie C h a r t A r ra y .p u s h ( [ d a t a [ i] . l a b e l , d a t a [ i] .va l u e ] ); } r e t u r n g o o g l e . v i s u a l i z a t io n . a r ra y T o D a t a T a b le ( pi e C h a r t A r r a y ) ; }; retu rn { r e s t r i c t : 'A ', scope: { c h a rtD a ta : ' = ' , c h a r t C o n fig : '= ' }, lin k :

f u n c t io n ( $ s c o p e , $elem ent)

{

g o o g le C h a r tL o a d e rP ro m is e . t h e n (fu n c ti o n () { v a r c h a rt = new g o o g l e . v i s u a l i z a t io n . P i e C h a r t ( $ e le m e n t [0 ]); $ s c o p e . $ w a t c h ( 'c h a r t D a t a ', fu n c t io n (n e w V a l, o ld V a l) v a r c o n f ig = $ s c o p e . c h a r t C o n fig ; i f (newVal) { c h a rt.d ra w ( co n v e rtT o P i e C h artD ataT a b le F o rm at( c o n f ig .fir s t C o lu m n H e a d e r , co n fig .se co n d C o lu m n H e a d e r, new Val), { t i t l e : $ s c o p e . c h a r t C o n f ig . t i t l e } ) ;

{

} },

tru e );

}); } }; }]);

Jeśli pom inąć wywołania A PI Google Charts, to dyrektywa p i e C h a r t na poziom ie koncepcyjnym nie jest wcale taka skomplikowana. O to szczegółowy opis je j budowy: 1. Dyrektywa p i e C h a r t zależy od wcześniej zdefiniowanej przez nas usługi, więc wstrzykujemy je j tę usługę. 2. Z definiow aliśm y fu n k cję o nazwie c o n v e r t T o P i e C h a r t D a t a T a b l e F o r m a t , p o b ierającą dane z naszego kontrolera i konw ertującą je na form at, w którym m ogą zostać przesłane do A PI Google Charts. 3. D efiniujem y całkiem standardową dyrektywę z izolowanym zakresem definiującym atrybuty, które m ają zostać do niej przekazane. 4. W fu n k cji l i n k w ykorzystujem y ob ietn icę zw róconą z usługi i w ykonujem y sw oją pracę w procedurze obsługi pow odzenia w bloku t h e n tej obietnicy. D zięki tem u m am y pewność, że nie wywołamy A PI Google Charts, zanim nie zostanie ono w pełni załadowane. 5. W procedurze obsługi powodzenia obietnicy tworzymy egzemplarz wykresu kołowego w ele­ mencie, w którym aktualnie się znajdujemy. Dzięki temu nie musimy szukać jakiegoś losowego elem entu ani wykorzystywać selektorów identyfikatorow ych, przez które nasza dyrektywa byłaby trudniejsza w użyciu.

242

|

Rozdział 13. Zaawansowane opcje definicji dyrektyw

6. Następnie dodajem y czujkę na polu c h a r t D a t a w zakresie oraz przekazujemy je j funkcję do wywołania jako drugi argum ent i wartość logiczną t r u e jako trzeci argument. W ten sposób stosujem y do $ s c o p e . c h a r t D a t a technikę zwaną w AngularJS głęboką obserwacją (ang. d eep w atch ). Jeśli wartość obserwowanego pola (albo jakiegokolw iek elementu w n im ) się zmieni, nastąpi wywołanie funkcji. 7. Funkcja c h a n g e jest wywoływana zarówno ze starą, ja k i nową wartością. Gdy otrzym am y p o­ prawną nową wartość, rysujem y wykres po uprzednim przekonwertowaniu danych z form atu przekazanego dyrektywie na form at rozpoznawany przez Google Charts. 8. Kiedy dane w AngularJS ulegną zmianie (z inicjatywy użytkownika lub serwera), funkcja ta zostanie wywołana automatycznie, więc nie m usim y nic więcej robić, aby zapewnić aktuali­ zowanie wykresu. Jeśli teraz otworzysz plik in d ex .h tm l w przeglądarce, zobaczysz wykres kołow y przedstaw iający początkow e dane. M ożesz kliknąć znajdujący się na stronie przycisk, żeby zaktualizow ać dane w kontrolerze. Inform acje te są przekazywane bezpośrednio przez referencję, więc dyrektywa je otrzyma i nastąpi wywołanie funkcji je obserwującej. W efekcie nastąpi automatyczna aktualiza­ cja wykresu najnowszym i wartościam i i interfejsu użytkownika. Gdybyśmy tylko chcieli dodać element danych albo restrykcyjnie kontrolować sposób i czas aktuali­ zacji wykresu, moglibyśmy dodać odpowiednią logikę do czujki. Poniżej przedstawiamy zestawienie czynności, które należy wykonać w prawie każdej dyrektywie nastaw ionej na prezentację danych: • odczekanie na załadowanie interfejsu API; • przekazanie danych do dyrektywy; • przekonwertowanie danych na odpowiedni form at i ich początkowa prezentacja; • obserwowanie danych i aktualizowanie interfejsu użytkownika w razie potrzeby.

Najlepsze praktyki W iesz już, ja k tw orzyć praktycznie wszystkie rodzaje dyrektyw o dowolnym stopniu złożoności, więc teraz chcielibyśmy, abyś poznał kilka zasad, których warto przestrzegać, by tw orzone przez Ciebie dyrektywy działały zgodnie z założeniam i i możliwie szybko w każdych warunkach.

Zakresy Jeśli w swojej dyrektywie dodajesz zmienne i funkcje do zakresu w elemencie l i n k lub c o n t r o l l e r , to powinieneś ustawić klucz s c o p e w obiekcie definicji dyrektywy na t r u e albo utworzyć izolowany zakres dla tej dyrektywy. Na przykład powiedzmy, że nasz kontroler zawiera zm ienną logiczną o nazwie s e l e c t e d , okre­ ślającą, czy pewne pole wyboru je st zaznaczone, czy nie. Gdyby nasze dyrektywy t a b s i t a b nie tw orzyły now ego zakresu bądź nie używały zakresu izolow anego, to przesłoniłyby zm ienną se l e cte d

z kontrolera, co powodowałoby najróżniejsze niepożądane skutki.

Najlepsze praktyki

| 243

Jeżeli dyrektywa wymaga dostępu do fu nkcji i zm iennych z zakresu nadrzędnego, to m am y do wyboru dwie m ożliw ości: • Utworzyć zakres potom ny i dodać do niego dowolne zmienne i funkcje. W tym celu w obiekcie definicji dyrektywy m ożna ustawić opcję s c o p e na t r u e . Ale jeśli zakres nadrzędny zawiera jakiekolw iek funkcje lub zm ienne, to będą one dostępne w dyrektywie. • Utworzyć zakres izolowany oraz przekazywać wszelkie zm ienne i funkcje przy użyciu wiąza­ nia danych i fu nkcji. Jest to idealne rozw iązanie, ponieważ wyklucza ryzyko, że dyrektywa będzie potrzebowała do działania określonej zm iennej albo funkcji z zakresu nadrzędnego. Dyrektywy z zakresem izolowanym najlepiej nadają się do wielokrotnego wykorzystania.

Sprzątaj i niszcz Gdy użytkownik korzysta z wiązań i innych dyrektyw, system AngularJS dodaje procedury nasłu­ chowe i czujki um ożliw iające m u aktualizow anie interfejsu użytkownika. A by żadna z n ich nie wyciekła ani nie pozostała w tyle, AngularJS usuwa je w m om encie niszczenia ich zakresów lub elementów. Kiedy program ista tworzy dyrektywę AngularJS z własnym zakresem (potom nym bądź izolowa­ nym ), to wszelkie czujki dodane na tym zakresie i procedury nasłuchowe dodane na elemencie przekazanym do tej dyrektywy są autom atycznie kasowane w chw ili zniszczenia tej dyrektywy w interfejsie użytkownika. System AngularJS nie może jednak skasować procedur nasłuchu zdarzeń dodanych przez nas do elem entów znajdujących się poza zakresem ani kodu H TM L dyrektywy. Gdy dodamy do aplikacji te proced u ry albo czu jki, posprzątanie ich po zniszczeniu dyrektywy je s t naszym zadaniem . N asłuch zdarzeń zniszczenia dyrektywy m ożna prow adzić na dwa sposoby: N asłuchiw anie zd arzenia $destroy n a zakresie Jak pokazaliśmy wcześniej, procedury nasłuchowe m ożna dodawać dla samego zakresu. Każdy zakres rozgłasza zdarzenie o nazwie $ d e s t r o y , które stanowi sygnał, że za chwilę zostanie znisz­ czony i skasowany. Zdarzenia tego może nasłuchiwać każdy kontroler i każda dyrektywa, aby wykonać dodatkowe czynności, gdy ono nastąpi. W procedurze obsługi zdarzenia $ d e s t r o y muszą zostać skasowane wszelkie procedury nasłuchowe, które dodamy ręcznie, lub interwały i lim ity czasu, które aktualnie trwają. Poniżej znajduje się prosta procedura nasłuchowa: $ s c o p e . $ o n ( '$ d e s t r o y ',

f u n c t io n ( )

{

/ / miejsce na kod porządkow y });

N asłuchiw anie zd arzenia $destroy n a elem encie Jeśli zakres je st odziedziczony (nie je st now y ani izolow any), a m im o to m usim y wykonać czynności porządkowe w m om encie niszczenia dyrektywy, rozwiązaniem jest nasłuchiwanie zdarzenia $ d e s t r o y na elemencie. Jest to zdarzenie jQ u ery wyzwalane przez AngularJS przed usunięciem elementu ze struktury DOM . Przykładowa procedura obsługi tego zdarzenia może wyglądać tak: $ e le m e n t . $ o n ( '$ d e s t r o y ',

f u n c t io n ( )

{

/ / miejsce na kod porządkow y });

244

|

Rozdział 13. Zaawansowane opcje definicji dyrektyw

Czujki W systemie AngularJS istnieje m ożliwość dodania własnych procedur nasłuchu zdarzeń (zwa­ nych czujkam i — ang. w atch er — w term inologii AngularJS) do zm iennych i funkcji w zakresie. Są one urucham iane przez A ngularJS w m o m en cie zm odyfikow ania obserw ow anej zm ien nej i program ista w takim przypadku m a dostęp zarówno do starej, ja k i now ej wartości. Istnieje kilka rodzajów czujek i warto wiedzieć, jakie cechy m a każdy z nich: $watch Standardowa czujka, która pobiera: • łańcuch będący nazwą zm iennej z zakresu; • funkcję, której wartość zwrotna jest obliczana. W obu przypadkach, jeśli wartość ulegnie zmianie (wykonywany jest prosty płytki test), nastę­ puje wywołanie przekazanej do czujki jako drugi argum ent funkcji ze starą i nową wartością. G łęb oka w ersja $watch Taka sama czujka jak standardowa, ale przyjmująca jako trzeci argument logiczną wartość true. Zmusza ona system AngularJS do rekurencyjnego sprawdzenia wszystkich obiektów i kluczy w obiekcie lub zm iennej i użycia funkcji an g u lar.eq u als do porównywania obiektów. Oczy­ wiście czujka ta wykryje wszystkie zm iany, ale zużyje też więcej cyklów procesora. Dlatego nie należy je j nadużywać w swoich aplikacjach. Lepszym rozwiązaniem może być utworzenie zm iennej logicznej sygnalizującej wewnętrzne zmiany i obserwowanie tej zm iennej. $watchCollect1on Nieco zoptymalizowana wersja czujki przeznaczona dla tablic. Pobiera podobne argumenty ja k $watch, ale przekazywana wartość powinna być łańcuchem . Funkcja jest wywoływana za każdym razem, gdy w tablicy zostaje dodany, usunięty lub przesunięty element. Czujka ta nie obserwuje zm ian indywidualnych własności elem entów w tablicy.

Funkcje $apply i $digest N ajczęściej występującym błędem przy integracji program ów z zewnętrznymi kom ponentam i jest brak aktualizacji interfejsu użytkownika, m im o że wszystko zostało popraw nie skonfigurow ane i połączone. Bardzo często przyczyną tych problem ów jest brak wywołania funkcji $apply() albo ręcznego uruchom ienia cyklu obliczeniowego za pom ocą wywołania funkcji $ d ig e s t ( ) . Jeśli pracujesz z zewnętrznymi kom ponentam i, zawsze pam iętaj, że w grze są dwa osobne cykle życia. Pierwszy to cykl życia AngularJS, który odpowiada za aktualizowanie interfejsu użytkowni­ ka, a drugi to cykl życia kom ponentu zew nętrznego. N a styku tych dwóch cyklów program ista musi powiadomić system AngularJS, że zmieniło się coś poza jego cyklem życia i że należy dokonać aktualizacji interfejsu użytkownika. I do tego służy funkcja $sco p e.$ a p p ly (), która rozpoczyna cykl obliczeniowy na $rootScope. Czasami wszystkim automatycznie zajm uje się inne zdarzenie w AngularJS, ale jeśli aktualizujesz zmienne zakresowe w odpowiedzi na zewnętrzne zdarzenia, pamiętaj, aby ręcznie wywołać funkcję $scop e.$ap p ly() lub $ s c o p e .$ d ig e s t().

Najlepsze praktyki

| 245

Podsumowanie W rozdziale tym zgłębiliśmy tajn ik i najbardziej skomplikowanych elem entów systemu AngularJS i opisaliśmy niektóre rzadziej używane, choć bardzo przydatne składniki i dyrektywy. Utworzyli­ śmy widżet giełdowy pobierający niestandardowe szablony, który dzięki tem u m ożna dostosować do indyw idualnych potrzeb za pom ocą prostej tran skluzji szablonów. Utw orzyliśm y też bardzo uproszczoną wersję jednopow tórzeniow ej pętli, wykorzystując zaawansowaną transkluzję i funk­ cje transkluzji. Ponadto pokazaliśmy, ja k nawiązać kom unikację między dyrektywami t a b s i t a b za pomocą kontrolerów dyrektyw oraz jak zastosować istniejące kontrolery i dyrektywy, np. ngM od e l , do tworzenia dyrektyw wejściowych typu suwaki i walidatory. W dalszej części rozdziału opisaliśmy konfigurację obiektu definicji dyrektywy reprezentującego deklaratywną dyrektywę f o r m - e le m e n t , generującą dynamiczne szablony przy użyciu etapu kom pilacji dyrektyw. Później udowodniliśmy, że utworzenie dyrektywy wyświetlającej wykres kołowy za pom ocą API Google Charts nie jest trudne i opisaliśmy podstawowe kwestie dotyczące budowy kom ponentów wizualnych oraz integrowania z aplikacją zewnętrznych widżetów interfejsu użytkownika. Na za­ kończenie przedstawiliśmy kilka porad na tem at tworzenia solidnych dyrektyw — przestrzeganie tych porad pozwala uniknąć kłopotów z wydajnością i dziwnych błędów. Znasz ju ż wszystkie najważniejsze rdzenne składniki systemu AngularJS. W następnym rozdziale przedstawiamy m etody pisania kom pletnych testów dla AngularJS przy użyciu narzędzia do wy­ konywania testów Protractor.

246

|

Rozdział 13. Zaawansowane opcje definicji dyrektyw

____________________ ROZDZIAŁ 14.

Testowanie kompleksowe

P o z n a ł e ś j u ż w s z y s t k i e t r y b y w c h o d z ą c e w s k ł a d m a s z y n y b ę d ą c e j a p lik a c j ą A n g u l a r J S , c z y l i k o n ­ t ro le r y , w id o k i , u s łu g i, f ilt r y i d y r e k t y w y . W i e s z też, j a k w a ż n e je st t e s t o w a n ie j e d n o s t k o w e , i u m ie s z p rz e te sto w a ć in d y w id u a ln ie k a ż d ą czę ść s y s t e m u A n g u la r J S . D o b r y z e sta w te stó w j e d n o s t k o w y c h p o z w a l a z a o s z c z ę d z i ć o g r o m n ą i l o ś ć c z a s u , k t ó r y in a c z e j t r z e b a b y b y ł o p r z e z n a c z y ć n a d e b u g o w a n ie , z a p o b ie g a n i e p o w s t a w a n i u b ł ę d ó w i w y s z u k i w a n i e r e g re s ji. A l e t e s t y j e d n o s t k o w e s p r a w d z a j ą s ię t y lk o d o p e w n e g o s t o p n ia . Z a i c h p o m o c ą m o ż n a s ię u p e w ­ n ić , ż e a p lik a c j a d z ia ł a p o p r a w n i e , le c z

p rz y z a ło ż en iu , ż e s e r w e r d z ia ł a w o k r e ś l o n y s p o s ó b . P r z e ­

k o n a l i ś m y s ię o t y m p o d c z a s t e s t o w a n i a u s ł u g i a s y n c h r o n i c z n y c h w y w o ł a ń s e r w e r o w y c h w r o z ­ d z ia l e 7., w k t ó r y m i m i t o w a l i ś m y s e r w e r z a p o m o c ą u s ł u g i $ h t t p B a c k e n d . U m o ż l i w i ł o n a m to s z y b k i e w y k o n y w a n i e t e s t ó w j e d n o s t k o w y c h , k t ó r e b y ł y n i e z a w o d n e , s t a b il n e i b a r d z o s z y b k i e . T e s t y te p o z w a l a j ą p r z e c h w y c i ć l o g i k ę k o n t r o l e r ó w i u s ł u g , a le c o z r o b ić , j e ś l i s e r w e r z m i e n i a w a r t o ś ć z w r o t n ą ? A l b o c o s ię s t a n ie , j e ż e li z m i e n i ą s ię a d r e s y U R L n a s e r w e r z e ? C o z f o r m a t o w a ­ n i e m i w y ś w i e t l a n i e m t r e ś c i H T M L , z w ł a s z c z a g d y z r o b i m y lit e r ó w k ę w w y r a ż e n i u n g - b i n d ? A b y w y k r y ć t a k ie b ł ę d y , m u s i m y n a p i s a ć t e s t y k o m p l e k s o w e , k t ó r e u r u c h a m i a j ą p r z e g l ą d a r k ę , u r u c h a m i a j ą n o r m a l n ą w e r s ję a p lik a c j i i k lik a j ą w n ie j ta k , j a k b y t o r o b i ł n o r m a l n y u ż y t k o w n i k . D o t e g o p o t r z e b n e je st n a r z ę d z i e o n a z w ie P r o t r a c t o r ( h ttp s://g ith u b .co m /a n g u la r/p ro tra ctor ). W

t y m r o z d z ia l e p r z e d s t a w i a m y b u d o w ę b a r d z o p r o s t e g o t e s t u k o m p l e k s o w e g o d la d e m o n s t r a ­

c y jn e j a p lik a c ji. D e f i n i u j e m y k o n f i g u r a c j ę n a r z ę d z i a P r o t r a c t o r , p i s z e m y k o m p l e k s o w y test i s p r a w ­ d z a m y , j a k d z ia ła . P o n a d t o o p i s u j e m y w s t ę p n ą k o n f i g u r a c j ę i w y m a g a n i a , j a k ie n a l e ż y s p e ł n ić , b y w y k o n a ć te testy, j a k r ó w n ie ż p r e z e n t u j e m y n a jle p sz e p r a k t y k i p r a c y z n im i. P o p r z e s t u d io w a n iu te g o r o z d z i a ł u b ę d z i e s z z n a ł n a r z ę d z ie P r o t r a c t o r i b ę d z i e s z w ie d z ia ł, j a k p i s a ć t e s t y p r z y j e g o u ż y c iu .

Do czego służy Protractor D l a c z e g o w y b r a l i ś m y n a r z ę d z i e P r o t r a c t o r , a n i e j a k ie ś i n n e ? P i e r w s z y m n a r z ę d z i e m m a j ą c y m u ł a t w i ć p i s a n i e k o m p l e k s o w y c h t e s t ó w d la a p lik a c j i A n g u l a r J S b y ł A n g u l a r J S S c e n a r i o R u n n e r

( h ttp ://co d e.an g u larjs.o rg /L 2 .1 6 /d o cs/g m d e/e2 e-testin g ) . B y ł t o k o m p l e t n y s y s t e m d o w y k o n y w a ­ n ia k o m p le k s o w y c h te stó w r o z p o z n a w a n y p rz e z A n g u la r J S , d z ię k i c z e m u te sty w y k o n y w a n e p r z y j e g o u ż y c i u b y ł y s t a b iln e i o b lic z a ln e .

247

Zrozum ieliśm y, że symulowanie czynności użytkowników, takich ja k klikanie i wpisywanie po­ przez JavaScript, nie było idealnym rozw iązaniem i nie pozwalało w pełni odtworzyć norm alnej sesji użytkowej. Dlatego tw órcy narzędzia Protractor postanowili wykorzystać jako podstawę coś takiego ja k Selenium W ebD river (h ttp ://d ocs.selen iu m h q.org /p ro jects/w ebd riv er/), czyli rozwiąza­ nie działające na poziom ie systemu operacyjnego i pozwalające naśladować prawdziwe kliknięcia myszą oraz naciśnięcia klawiszy. Ale jednocześnie nadal chcem y unikać jednego z największych problem ów dotyczących kom plek­ sow ych testów aplikacji A JA X , czyli oczekiw ania n a załadowanie strony. Podczas przeglądania norm alnej strony przez użytkownika bez problem u m ożem y sprawdzić, kiedy zakończyło się ła ­ dow anie strony, i w odpow iedzi na kliknięcie od n ośnika m ożem y d okon ać je j przeładow ania w całości. Dzięki temu dokładnie wiemy, kiedy strona zostaje załadowana, i m ożem y kontynu­ ować testowanie. Z aplikacjami jednostronicowym i jest inaczej, ponieważ ładowana jest tylko jedna strona. Wszystkie dane mogą być (i z reguły są) pobierane asynchronicznie nawet po zakończeniu ładowania strony. Skąd w takim razie wiadomo w teście, kiedy sprawdzić, czy określony elem ent danych został po­ kazany? M am y dwie możliwości: • Czekamy przez pewien arbitralnie określony czas po załadowaniu strony lub kliknięciu od ­ nośnika — około pięciu sekund. • Przed wykonaniem testów czekamy na pojawienie się na stronie określonego elementu. Obie te metody są bardzo zawodne. Wystarczy, że jakieś wywołanie serwerowe potrwa 5,1 sekundy, i test zakończy się niepowodzeniem . Potem rozpoczynam y oczekiwanie na następne wykonanie testu, nawet jeśli w teście nie uda się wyłapać tych niedeterministycznych niepowodzeń. I w końcu przestajem y ufać testom kompleksowym. P ro tracto r spraw ia, że problem oczekiw ania przez arb itraln ą ilość czasu na zdarzenia znika. Narzędzie to jest zbudowane na bazie W ebD rivera, ale rozpoznaje AngularJS. Zatem jeśli zostanie kliknięty przycisk i wysłane zostanie wywołanie serwerowe, Protractor wie, że musi poczekać na zwrot z tego wywołania, zanim wykona dalszą część testu. Dzięki temu program ista może skupić się na pisaniu testu, który zostanie wykonany podobnie ja k w przypadku działania użytkownika, bez potrzeby stosowania warunków i lim itów czasu dotyczących pojawiania się lub znikania okre­ ślonych elementów.

Konfiguracja wstępna Protractor to pakiet NodeJS, więc m ożna go zainstalować za pom ocą polecenia npm (oczywiście do tego konieczna jest instalacja N odeJS): sudo npm i n s t a l l

-g p ro tra c to r

Polecenie to spowoduje zainstalowanie narzędzia Protractor wraz ze wszystkimi jego zależnościami jako globalnego pakietu do użytku w różnych projektach.

248

|

Rozdział 14. Testowanie kompleksowe

Dodatkowo potrzebujem y narzędzia W ebD river do urucham iania i kontrolow ania przeglądarek, w których w ykonujem y testy jednostkowe. Po zainstalowaniu Protractora otrzym ujem y skrypty potrzebne do pobrania i zainstalowania także W ebD rivera. W ykonaj tylko następujące polecenie: sudo w e bd rive r-m an ag e r update

W tym m o m en cie m am y wszystkie narzędzia potrzebne do w ykonyw ania testów P rotractor. A wykonywanie tych testów sprow adza się do w ykonania poniższego polecenia: p ro tra c to r p a th / to / p ro tra c to r.c o n f.js

Jak wygląda zawartość pliku p rotractor.con f.js? Dowiesz się tego w następnym podrozdziale.

Konfiguracja narzędzia Protractor K onfiguracja narzędzia Protractor znajduje się w pliku JavaScript zawierającym wszystkie opcje potrzebne do wykonywania testów kom pleksowych za pom ocą tego programu. Zaliczają się do nich następujące ustawienia: • adres serwera; • adres Selenium W ebD river, na którym m ają być wykonywane testy; • testy do wykonania; • lista przeglądarek do użycia w testach. To oczywiście nie wszystkie ustawienia. Poniżej przedstawiamy przykładową konfigurację zawiera­ jącą większość powszechnie używanych opcji, którą wykorzystamy w przykładach w tym rozdziale: / / Plik: r14/protractor.conf.js e x p o r t s . c o n f ig = {

/ / adres serwera Selenium s e le n i um Address:

' h t tp :/ / lo c a lh o s t :4 4 4 4 / w d / h u b ',

/ / adres testowanego serwera b a se U rl:

' h t t p : / / lo c a l h o s t : 8 0 0 0 / ',

/ / opcje do przekazan ia do WebDrivera c a p a b ilit ie s :

{

'b ro w se rN a m e ':

'ch ro m e '

},

/ / wzorce specyfikacji odnoszą się do lokalizacji pliku specyfikacji i mogą zawierać wzorce globalne sp e c s:

[ ' * S p e c * . j s '] ,

/ / opcje do przekazania do węzła Jasm ine jasm in e N od e O p ts:

{

sh o w C o lo rs: t ru e / / włącza kolory w raporcie w wierszu poleceń } };

Konfiguracja narzędzia Protractor

| 249

Je st t o n a j p r o s t s z y m o ż l i w y p l i k k o n f i g u r a c y j n y n a r z ę d z i a P r o t r a c t o r . Z a w i e r a o n n a s t ę p u j ą c e u s t a w ie n ia : • S e r w e r S e l e n i u m d z i a ł a l o k a l n i e n a p o r c ie 4 44 4. • S e r w e r d z ia ł a p o d a d r e s e m

h ttp ://lo calh o st:8 0 0 0 / .

• A u t o m a t y c z n ie m a b y ć u r u c h a m ia n a p r z e g lą d a r k a C h r o m e . • P lik

spec.js z a w i e r a k o d t e s t u k o m p l e k s o w e g o .

• O p c j a k o n f i g u r a c y j n a J a s m in e w łą c z a j ą c a k o l o r y w w i e r s z u p o le c e ń . S z c z e g ó ło w e in f o r m a c j e n a t e m a t o p c ji k o n f ig u r a c y j n y c h n a r z ę d z ia P r o t r a c t o r m o ż n a z n a le ź ć w p l i k u k o n f i g u r a c j i re fe r e n c y j n e j ( https://g ithu b.com /an g u lar/p rotractor/blob/m aster/d ocs/referen ceC on f.js ) w p o r t a lu G i t H u b . C o trz e b a te ra z z ro b ić , a b y w y k o n a ć te st? P r z e jd ź d o f o ld e r u

r14 z p l i k ó w p o b r a n y c h z s e r w e r a

F T P i w y k o n a j n a s tę p u ją c e c z y n n o ś c i: 1. U r u c h o m lo k a ln ie S e l e n i u m ( m o ż n a t o z r o b i ć z a p o m o c ą p o l e c e n ia w e b d r iv e r - m a n a g e r s t a r t ) . 2. U r u c h o m lo k a ln ie s e r w e r ( w t y m p r z y p a d k u n o d e s e r v e r . j s ) . 3. U r u c h o m n a r z ę d z ie P r o t r a c t o r ( p r o t r a c t o r t e s t / e 2 e / p r o t r a c t o r . c o n f . j s ) . A l e n a j p i e r w p r z y j r z y m y s ię n a s z e m u t e s t o w i.

Test kompleksowy T e s t y P r o t r a c t o r w y k o r z y s t u j ą tę s a m ą s k ł a d n i ę J a s m in e , k t ó r e j u ż y w a m y d o t e s t ó w j e d n o s t k o ­ w y c h , w ię c m o ż e m y p o s ł u g i w a ć s ię b l o k a m i d e s c r i b e d la z b i o r ó w t e s t ó w i i n d y w i d u a l n y m i b l o ­ k a m i i t d la p o j e d y n c z y c h testów . D o d a t k o w o P r o t r a c t o r u d o s t ę p n ia k ilk a z m ie n n y c h g lo b a ln y c h , k tó re są p o trz e b n e d o p is a n ia te stó w k o m p le k s o w y c h : bro w se r Je st t o o p a k o w a n i e d l a W e b D r i v e r a u m o ż l i w i a j ą c e b e z p o ś r e d n i ą in t e r a k c j ę z p r z e g lą d a r k ą . O b ie k t u te g o u ż y w a m y d o w c h o d z e n ia n a r ó ż n e s t r o n y i s p r a w d z a n ia in f o r m a c j i n a s tro n a c h . e le m e n t O b i e k t e le m e n t to f u n k c j a p o m o c n i c z a u m o ż liw ia j ą c a z n a j d o w a n ie e le m e n t ó w H T M L i in te ra k c ję z n i m i . Z n a j d u j e o n a e le m e n t y j a k o a r g u m e n t y , a n a s t ę p n ie z w r a c a e le m e n t , z k t ó r y m m o ż n a w c h o d z i ć w in t e r a k c j e , n p . k li k a j ą c g o i w y s y ła j ą c d o n i e g o z d a r z e n i a n a c i ś n i ę c i a k la w is z y . by Je st t o o b ie k t z a w ie r a j ą c y k o l e k c j ę s t r a t e g ii z n a j d o w a n i a e le m e n t ó w . E l e m e n t y m o ż n a w y s z u ­ k iw a ć z a p o m o c ą s t a n d a r d o w y c h s t r a t e g ii W e b D r i v e r a , c z y li w e d ł u g i d e n t y f ik a t o r ó w i k la s C S S . P o n a d t o P r o t r a c t o r d o d a j e k i l k a w ł a s n y c h s tr a t e g ii p o z w a la j ą c y c h w y s z u k i w a ć e le m e n t y w e d ł u g m o d e l u (m o d e l), w i ą z a n ia ( b i n d i n g ) i p ę t li ( r e p e a t e r ) , k t ó r e s ą t y p o w e d la s y s t e m u A n g u la r J S . B e z z b ę d n e g o d a l s z e g o g a d a n i a s p ó j r z m y t e r a z n a t e s t d l a a p l ik a c j i t r a s u j ą c e j , k t ó r ą n a p i s a l i ś m y w r o z d z i a l e 10. (jej k o d z n a j d u j e s ię w f o ld e r z e

r1 4 ). A b y u r u c h o m i ć tę a p lik a c j ę , n a j p i e r w z a i n ­

s t a lu j p o t r z e b n e p a k i e t y z a p o m o c ą p o l e c e ń npm i n s t a l l i n o d e s e r v e r . j s :

250

|

Rozdział 14. Testowanie kompleksowe

/ / Plik: rM/simpleRoutingSpec.js d e s c r i b e ( 'R o u t i n g T e s t ',

f u n c t io n ( )

{

it ( 'P o w i n i e n w y ś w ie t lić d ru żyn y na p ie rw sz e j s t r o n i e . ',

f u n c t io n ( )

{

// otwiera stronę z listą drużyn b ro w s e r.g e t('/ ') ;

// sprawdzenie, czy w pętli jest pięć wierszy v a r rows = e le m e n t . a ll( b y . r e p e a t e r ( 'D r u ż y n a w t e a m L i s t C t r l. t e a m s ') ) ; e x p e c t ( r o w s . c o u n t ( ) ) . t o E q u a l( 5 ) ;

// sprawdza szczegóły pierwszego wiersza v a r firstR o w R a n k = elem ent( b y . r e p e a t e r ( 'D r u ż y n a w t e a m L is t C t r l. t e a m s ') . r o w (0 ). c o lu m n ( ' t e a m . r a n k ') ); v a r firstRow N am e = elem ent( b y . r e p e a t e r ( 'D r u ż y n a w t e a m L is t C t r l. t e a m s ') . r o w (0 ). c o lu m n ( ' te a m .n a m e ')); e x p e c t ( f ir s t R o w R a n k . g e t T e x t ( ) ) . toEqual ( ' 1 ' ) ; e x p e c t ( fir s t R o w N a m e . g e t T e x t () ) . toEqual ( 'H i s z p a n i a ') ;

// sprawdzenie szczegółów ostatniego wiersza v a r lastRow R ank = elem ent( b y . r e p e a t e r ( 'D r u ż y n a w t e a m L is t C t r l. t e a m s ') . r o w (4 ). c o lu m n ( ' t e a m . r a n k ') ); v a r lastRowName = elem ent( b y . r e p e a t e r ( 'D r u ż y n a w t e a m L is t C t r l. t e a m s ') . r o w (4 ). c o lu m n ( ' te a m .n a m e ')); e x p e c t ( la s t R o w R a n k . g e t T e x t ( ) ) . t o E q u a l( '5 ') ; e x p e c t ( la s t R o w N a m e . g e t T e x t () ) .t o E q u a l('U r u g w a j ') ;

// sprawdzenie, czy odnośnik logowania jest wyświetlony, a odnośnik wylogowywania jest ukryty e x p e c t ( e l e m e n t ( b y . c s s ( '. l o g i n - l i n k ') ) . i s D i s p l a y e d ( ) ) .to B e (tru e ); e x p e c t ( e le m e n t ( b y . c s s ( '. l o g o u t - l i n k ' ) ) . i s D i s p la y e d ( ) ) . t o B e (fa ls e ) ; }); i t ( 'P o w in i e n u m o żliw ia ć lo g o w a n ie . ',

f u n c t io n ( )

{

// przejście na stronę logowania b r o w s e r . g e t ( '# / l o g i n ') ; v a r username = elem ent( b y .m o d e l( ' lo g i n C t r l. u s e r . u s e r n a m e ') ); v a r passw ord = elem ent( b y .m o d e l( ' lo g i n C t r l . u s e r . p a s s w o r d ') );

/ / wpisuje nazwę użytkownika i hasło u s e rn a m e . s e n d K e y s ( 'a d m in '); p a s s w o r d . s e n d K e y s ('a d m in ');

// klika przycisk logowania e le m e n t ( b y . c s s ( ' . b t n . b t n - s u c c e s s ') ) . c l i c k ( ) ;

Test kompleksowy

| 251

/ / sprawdza, czy użytkownik na pew no został przekierowany e x p e c t ( b r o w s e r .g e t C u r r e n t U r l()) . t o E q u a l ( 'h t t p : / / lo c a l h o s t : 8 0 0 0 / # / ');

/ / sprawdzenie, czy odnośnik logowania jest ukryty, a odnośnik wylogowywania jest wyświetlony e x p e c t ( e le m e n t ( b y . c s s ( ' . l o g i n - l i n k ') ) . i s D i s p l a y e d ( ) ) . t o B e ( fa ls e ) ; e x p e c t ( e le m e n t ( b y . c s s ( ' . l o g o u t - l i n k ') ) . i s D i s p l a y e d ( ) ) . t o B e ( t r u e ); }); });

W przykładzie tym znajdują się dwa testy. Pierwszy test: • O tw iera stronę główną aplikacji z drużynami piłkarskimi. • Pobiera wszystkie wiersze za pom ocą pętli i sprawdza, czy na stronie głównej jest pięć wierszy. • Pobiera nazwę i ranking z pierwszego wiersza i sprawdza, czy wszystko jest w porządku. • Pobiera nazwę i ranking z ostatniego wiersza i sprawdza, czy wszystko jest w porządku. • Sprawdza, czy odnośnik logowania jest widoczny, a odnośnik wylogowywania jest ukryty. Zatem pierwszy test dotyczy wyłączenie renderowania i logiki i pozwala sprawdzić, czy aplikacja poprawnie łączy się z serwerem oraz czy prawidłowo pobiera i wyświetla treść. Drugi test dotyczy interakcji program u z użytkownikiem i sprawdza: • otwieranie strony logowania; • czy nazwa użytkownika i hasło są wprowadzane do odpowiedniego modelu; • kliknięcie przycisku logowania przy użyciu selektora CSS; • czy logowanie działa przez sprawdzenie adresu U RL strony, na którą następuje przekierowanie; • czy odnośnik logowania zostaje ukryty, a odnośnik wylogowania zostaje pokazany. Zwróć uwagę na brak w tych testach jakichkolw iek warunków dotyczących oczekiwania. N apisa­ liśmy te testy tak, jakby to użytkownik korzystał z aplikacji, a kwestie dotyczące ich obsługi pozo­ stawiliśmy do rozwiązania system om AngularJS i Protractor. A by przeprowadzić te testy, należy wykonać następujące czynności: 1. Jeśli serwer nie jest włączony, wykonaj polecenie node s e r v e r .js w folderze appU n derT est. M oże być konieczne uprzednie wykonanie polecenia npm i n s t a l l w tym folderze. 2. Jeżeli Selenium jeszcze nie działa, wykonaj polecenie webdriver-manager s ta r t. 3. W ykonaj polecenie p ro tra c to r p r o t r a c t o r .c o n f .js w folderze zawierającym plik konfigura­ cyjny i specyfikacje. Gdy wykonasz te czynności, Protractor uruchom i przeglądarkę Chrom e przy użyciu Selenium, wejdzie na główną stronę naszej lokalnie działającej aplikacji oraz wykona wszystkie zdefiniowane testy. Na koniec powinien wydrukować inform ację, czy testy zakończyły się pomyślnie, czy nie, oraz powód ewentualnej porażki.

252

|

Rozdział 14. Testowanie kompleksowe

Uwagi P o d c z a s p i s a n i a t e s t ó w k o m p l e k s o w y c h d l a a p lik a c j i A n g u l a r J S n a l e ż y m i e ć n a w z g lę d z ie p e w n e w a ż n e k w e s t ie o r a z d o b r z e je st z n a ć i s t o s o w a ć n a j le p s z e p r a k t y k i. P o n iż e j z n a j d u j e s ię i c h o p is :

L ok alizacja dyrektywy ng-app G d y p i s z e s z p r o s t y te st P r o t r a c t o r d la a p lik a c j i A n g u l a r J S i s k ie r u j e s z g o p o d j a k i k o l w i e k a d ­ r e s U R L , p o d k t ó r y m z n a j d u j e s ię a p li k a c j a A n g u l a r J S , P r o t r a c t o r d o m y ś l n i e b ę d z i e s z u k a ł d y r e k t y w y n g - a p p w z n a c z n i k u < b o d y > . K i e d y j ą z n a j d z ie , w ł ą c z y s ię i w y k o n a s w o j e z a d a n ia . A l e j e ś li d y r e k t y w a ta z o s t a n i e z a d e k l a r o w a n a d l a i n n e g o e le m e n t u , n a l e ż y o t y m f a k c ie p o ­ in f o r m o w a ć n a r z ę d z ie P r o tr a c to r. S łu ż y d o te g o o p c ja ro o t E le m e n t k o n f ig u r a c j i P ro tr a c to ra , k t ó r e j p r z e k a z u j e s ię s e le k t o r C S S e le m e n t u z d y r e k t y w ą n g - a p p . N a p r z y k ł a d j e ś li d y r e k t y w a ta je st z d e f i n i o w a n a w p o n i ż s z y s p o s ó b : < d iv c l a s s = " a n g u l a r - a p p " n g -ap p ="m y A p p ">< /d iv > to w k o n f i g u r a c j i P r o t r a c t o r a p o w i n n a z n a le ź ć s ię o p c j a r o o t E l e m e n t o n a s t ę p u j ą c e j t re śc i: ro o tE le m e n t:

". a n g u la r - a p p "

D z i ę k i t e m u P r o t r a c t o r b ę d z ie w ie d z ia ł, że p o t r z e b n y m u e le m e n t n a l e ż y d o k l a s y a n g u la r - a p p . Je żeli d y r e k t y w a n g - a p p je st z d e f i n i o w a n a d la e le m e n t u < b o d y > , o p c ja r o o t E l em ent je st z b ę d n a .

Sondow anie J e śli a p li k a c j a w y k o r z y s t u j e t e c h n i k ę s o n d o w a n i a , p o n i e w a ż p o b i e r a j a k ie ś in f o r m a c j e a lb o w y k o n u j e o b l ic z e n ia c o k i l k a s e k u n d , n i e n a l e ż y u ż y w a ć u s ł u g i A n g u l a r J S $ t im e o u t . P r o t r a c ­ t o r m a p r o b l e m y z o d g a d n i ę c ie m , k i e d y u s ł u g a ta k o ń c z y d z ia ła n ie . Je ż e li w ię c p o t r z e b u j e s z s o n d o w a n i a i c h c e s z je k o m p l e k s o w o p r z e t e s t o w a ć , w z a m i a n u ż y j u s ł u g i $ i n t e r v a l . P r o t r a c t o r d o b r z e z n i ą w s p ó łp r a c u j e .

R ęczny ro zru ch A k t u a l n i e P r o t r a c t o r n i e w s p ó ł p r a c u j e z r ę c z n ie u r u c h a m i a n y m i a p lik a c j a m i A n g u l a r J S . Je śli w ię c p o t r z e b u j e s z k o m p l e k s o w y c h t e s t ó w d l a t a k i c h a p lik a c j i, b ę d z i e s z m u s i a ł p r a c o w a ć z n a ­ r z ę d z i e m W e b D r i v e r ( w y w o ł u j ą c f u n k c j e b r o w s e r . d r i v e r . g e t z a m ia s t b r o w s e r . g e t itd .) o r a z d o d a ć lim it y c z a s u o c z e k iw a n ia , a b y u m o ż l iw ić z a ła d o w a n ie w s z y s t k ie g o , c o je st p o t r z e b n e , p r z e d r o z p o c z ę c i e m te stu . M u s i a ł b y ś z r e z y g n o w a ć z k o r z y ś c i, j a k ie z a p e w n i a P r o t r a c t o r .

W ykonyw anie w przyszłości P o l e c e n i a W e b D r i v e r , k t ó r e p i s z e m y w n a s z y c h t e s t a c h , n ie z w r a c a j ą w a r t o ś c i t y l k o o b ie t n ic e , k t ó r e z o s t a n ą p ó ź n ie j w y k o n a n e w p r z e g lą d a r c e ( a n a w e t w r ó ż n y c h p r z e g lą d a r k a c h ) . Z a t e m f u n k c j a c o n s o l e . l o g n i e z a r e j e s t r u j e ż a d n y c h w a r t o ś c i , p o n i e w a ż n ie b ę d ą o n e d o s t ę p n e w c z a s ie w y k o n y w a n i a k o d u .

D ebugow anie P r o t r a c t o r m a b o g a t y z e sta w n a r z ę d z i d ia g n o s t y c z n y c h , b o w y k o r z y s t u je n a r z ę d z ia d ia g n o ­ s t y c z n e W e b D r i v e r . A b y z d i a g n o z o w a ć w y b r a n y test, w y s t a r c z y d o d a ć p o n i ż s z y w ie r s z k o d u w m ie j s c u , w k t ó r y m c h c e m y r o z p o c z ą ć d e b u g o w a n ie : b r o w s e r .d e b u g g e r ( ) ;

Uwagi

| 253

Wywołanie to m ożna wpisać za dowolną linijką kodu w teście. Później należy uruchom ić testy za pom ocą poniższego polecenia: p r o t r a c t o r debug ś c ie ż k a / d o / c o n f . j s

Powoduje to uruchom ienie debugera Node, który umożliwia wykonywanie kodu między punktam i wstrzymania. W ystarczy nacisnąć klawisz C, a po nim E nter, aby nakazać Protractorow i kontynuow anie w ykonyw ania testów . P ro tracto r będzie w ykonał testy norm aln ie w przeglądarce aż do napotkania instrukcji debugera. W tym m om encie wstrzyma działa­ nie i będzie czekał na dalsze instrukcje. Jest to prawdziwa aplikacja działająca w przeglądarce, z którą m ożna współpracować i którą m ożna diagnozować w celu dowiedzenia się, co tak n a­ prawdę „widzi” system wykonawczy narzędzia Protractor. M ożna klikać różne obiekty i zmie­ niać stan testu, by spowodować jego niepowodzenie. Po zakończeniu diagnostyki m ożna na­ cisnąć klawisz C, a po nim E nter, żeby kontynuować wykonywanie testu do następnego punktu wstrzymania debugera lub do końca. O statnią rzeczą, którą należy rozważyć, je st organizacja testów w taki sposób, aby łatwo było je opanow ać i aby dało się je w ielokrotnie wykorzystać. W teście przedstaw ionym w poprzednim podrozdziale, służącym do znajdowania elementów na stronie i interakcji z nim i poprzez klikanie, wpisywanie tekstu i tworzenie asercji dotyczących stanu interfejsu użytkownika, użyliśmy kon­ strukcji element i by. Ale pisząc własne testy, należy utworzyć interfejs A PI pozwalający szybko się zorientować, do czego dany test służy. Dodatkową korzyścią jest to, że przy użyciu tego A PI każdy będzie mógł szybko zbudować zestaw większych i bardziej kompleksowych testów. W celu realizacji tego pomysłu należy wykorzystać pojęcie obiektów stron. W ram ach przykładu przepiszemy test listy drużyn piłkarskich z użyciem obiektów stron zamiast A PI W ebD rivera: / / Plik: r14/routingSpecWithPageObjects.js / / Najlepiej, gdy obiekty stron znajdują się w osobnych plikach, dzięki czemu łatwiej jest ich używać w różnych testach, // ale w tym przypadku dla ułatwienia wrzuciliśmy wszystkie do jednego worka. f u n c t io n T e a m sL istP a g e () t h is . o p e n = f u n c t io n ( ) b r o w s e r . g e t ( '/ ') ;

{ {

}; t h is.g e t T e a m s L ist R o w s = f u n c t io n ( ) { r e t u r n e le m e n t . a ll( b y . r e p e a t e r ( 'D r u ż y n a w t e a m L i s t C t r l. t e a m s ') ) ; }; t h is.g e tR a n k F o rR o w = fu n c t io n (ro w ) { r e t u r n elem ent( b y . r e p e a t e r ( 'D r u ż y n a w t e a m L is t C t r l. t e a m s ') .ro w (r o w ). colum n( ' t e a m .r a n k ') ) ; }; this.ge tN am e Fo rR o w = fu n c t io n (ro w ) { r e t u r n elem ent( b y . r e p e a t e r ( 'D r u ż y n a w t e a m L is t C t r l. t e a m s ') .ro w (r o w ). colum n( ' team.name') ) ; };

254

|

Rozdział 14. Testowanie kompleksowe

t h i s . i s L o g i n L i n k V i s i b l e = f u n c t io n ( )

{

r e t u r n e l e m e n t ( b y . c s s ( '. l o g i n - l i n k ') ) . i s D i s p l a y e d ( ) ; }; t h i s . i s L o g o u t L i n k V i s i b l e = f u n c t io n ( ) { r e t u r n e l e m e n t ( b y . c s s ( '. l o g o u t - l i n k ') ) . i sD i s p l a y e d ( ) ; }; } d e s c r ib e ( 'T e s t t ra s o w a n ia na b a z ie obie któw s t r o n . ',

f u n c t io n ( )

it ( 'P o w i n i e n w y św ie tla ć d ru ż yn y na p ie rw sz e j s t r o n i e . ', v a r te a m sL istP a g e = new T e a m sL is tP a g e ();

{

f u n c t io n ( )

{

team sLi s t P a g e . o p e n ( ) ; e x p e c t(te a m sL istP a g e .g e tT e a m sL i s t R o w s ( ) . c o u n t ( ) ) . t o E q u a l(5 ); e x p e c t(t e a m sL is tP a g e .g e t R a n k F o r R o w (0 ).g e t T e x t ( ) ) . t o E q u a l( ' 1 ') ; e x p e c t(te a m sL istP a g e .g e tN a m e F o rR o w (0 ).g e t T e x t ( ) ) . t o E q u a l( 'H i s z p a n i a '); e x p e c t(t e a m sL is tP a g e .g e t R a n k F o r R o w (4 ).g e tT e x t()) . t o E q u a l( ' 5 ') ; e x p e c t(te a m sL istP a g e .g e tN a m e F o rR o w (4 ).g e tT e x t()) . t o E q u a l( ' U ru g w a j') ;

// sprawdzenie, czy odnośnik logowania jest widoczny, a odnośnik wylogowywania jest ukryty e x p e c t ( t e a m s L is t P a g e . i s L o g in L in k V i s i b l e ( ) ) . t o B e ( t r u e ) ; e x p e c t ( t e a m s L is t P a g e . i s L o g o u t L in k V is i b l e ( ) ) . t o B e ( f a l s e ) ; }); }); U t w o r z y l i ś m y k la s ę J a v a S c r ip t o n a z w ie T e a m s L is t P a g e , u d o s t ę p n ia j ą c ą in t e r f e j s y A P I d o o t w ie ­ r a n i a s t r o n y , p o b i e r a n i a w s z y s t k i c h w i e r s z y i p o b i e r a n i a i n d y w i d u a l n y c h n a z w y i r a n k i n g u d la w y b r a n e g o w ie r s z a . N a s t ę p n i e p r a c u j e m y z e g z e m p l a r z e m k l a s y T e a m s L is t P a g e , d z i ę k i c z e m u test j e s t z n a c z n i e ł a t w ie j s z y w o d b i o r z e n i ż w c z e ś n ie j . P o d o b n y z a b i e g m o ż e m y w y k o n a ć d l a t e s t u s t r o n y lo g o w a n ia . O b i e k t y s t r o n r e p r e z e n t u j ą a b s t r a k c j e o k r e ś la j ą c e s p o s ó b d o s t ę p u d o w y b r a n y c h e le m e n t ó w i i n ­ t e r a k c j i z n i m i w j e d n y m m ie j s c u . D z i ę k i t e m u s ą ła t w e w u ż y c i u i m o d y f i k o w a n i u , p o n i e w a ż n ie t r z e b a w p r o w a d z a ć z m i a n w w ie l u m ie j s c a c h .

Podsumowanie Z a in s t a l o w a liś m y n a r z ę d z ie P r o t r a c t o r i u t w o r z y l iś m y k o n f ig u r a c j ę d la b a r d z o p r o s t e g o te stu k o m p l e k s o w e g o . P ó ź n ie j n a p i s a l i ś m y d w a t e s t y k o m p l e k s o w e . P i e r w s z y s p r a w d z a ł r e n d e r o w a n i e i l o g i k ę p r e z e n t a c y j n ą n a g ł ó w n e j s t r o n i e , a d r u g i t e s t o w a ł p r o c e s l o g o w a n i a w a p lik a c j i. P o n a d t o p r z e d s t a w i l i ś m y lis t ę w a ż n y c h k w e s t ii, o k t ó r y c h n a l e ż y p a m ię t a ć p r z y p i s a n i u t a k i c h te s t ó w .

Podsumowanie

| 255

M a j ą c z e s t a w t e s t ó w k o m p l e k s o w y c h ( a P r o t r a c t o r u m o ż l i w i a z d e f i n i o w a n i e w ie l u t a k i c h z e s t a ­ w ó w d o r ó ż n y c h c e ló w ) , m o ż n a s z y b k o i d o k ł a d n i e s p r a w d z i ć , c z y a p lik a c j a d z i a ł a p r a w i d ł o w o , b e z rę c z n e g o , ż m u d n e g o t e s t o w a n ia k a ż d e g o s k ła d n ik a o s o b n o . W

d u ż y c h a p lik a c j a c h z e s t a w t e ­

s t ó w k o m p l e k s o w y c h je st n i e o d z o w n y . Z n a s z j u ż p r a w i e w s z y s t k i e a s p e k t y s y s t e m u A n g u l a r J S , w ię c w n a s t ę p n y m r o z d z i a l e p r z e d s t a ­ w i a m y p o d s u m o w a n i e w i a d o m o ś c i , w k t ó r y m z a w a r l i ś m y n a j le p s z e p r a k t y k i i p o r a d y , k t ó r y c h w a r t o p r z e s t r z e g a ć p o d c z a s p i s a n i a a p lik a c j i A n g u l a r J S . P o n a d t o o p i s a l i ś m y k i l k a n a r z ę d z i i a p l i­ k a c ji, k t ó r e m o g ą u ł a t w ić C i p ra c ę .

256

|

Rozdział 14. Testowanie kompleksowe

_____________________ ROZDZIAŁ 15.

Porady i najlepsze praktyki

W poprzednich rozdziałach nauczyłeś się bardzo wiele na tem at systemu szkieletowego AngularJS i dogłębnie poznałeś prawie wszystkie jego części. W prawdzie jeszcze wiele pozostaje Ci do na­ uczenia się, ale masz ju ż solidne podstawy do tego, by m óc spojrzeć na ten produkt z szerszej per­ spektywy. W tym rozdziale chcem y właśnie nie skupiać się na konkretnych funkcjach, tylko na ogólnych kwestiach. Poruszam y więc następujące tematy: • testowanie, • struktura plików i katalogów, • najlepsze praktyki, • budowa aplikacji, • narzędzia i biblioteki. Pokażem y Ci, ja k efektywnie pracow ać z system em szkieletowym AngularJS oraz ja k tworzyć aplikacje, które będą dobrze służyły przez długi czas, nie spow alniając tem pa pracy.

Testowanie Pierwszą i najważniejszą zasadą tworzenia aplikacji sieciowych jest pisanie testów p r z e d rozpo­ częciem pisania aplikacji, p o d c z a s je j tworzenia oraz p o zakończeniu pracy. Staraliśm y się wpoić Ci te zasady w niniejszej książce przez przedstawianie testów jednostkow ych i kom pleksow ych wszędzie, gdzie się tylko dało.

Programowanie oparte na testach Pisanie testów jednostkowych i specyfikacji z góry jest najlepszą metodą tworzenia dużych i łatwych w obsłudze serwisowej aplikacji AngularJS. Najważniejsze powody, dla których warto tworzyć te­ sty jednostkow e, opisaliśmy w rozdziale 3. W tym podrozdziale przedstawiamy najlepsze praktyki i zasady, których należy przestrzegać przy pisaniu testów dla swoich projektów.

257

Różnorodność testów N iektórzy twierdzą, że zastosowanie m etodyki T D D (ang. test-driven d ev elop m en t) pozwala cał­ kowicie pozbyć się błędów z programu. Oczywiście w praktyce metodyka ta doskonale sprawdza się w zapewnianiu jakości szczegółów im plem entacyjnych, ale na większą skalę, w odniesieniu do złożonych aplikacji, testy jednostkow e nie są w stanie wychwycić wszystkich m ożliw ych błędów i usterek. Dlatego tak ważne jest utworzenie zestawu testów jednostkow ych, integracyjnych i sce­ nariuszowych, dzięki którym m ożna m ieć pewność co do działania swojej aplikacji. T esty jed nostkow e Test jednostkow y jest zwięzły i skoncentrow any na testowaniu tylko jednego elementu (kon­ trolera, usługi, filtru itd.) i jed n ej funkcji. Za pom ocą tego rodzaju testów (które tw orzy się przy użyciu narzędzia Karm a, opisanego w rozdziale 3.) m ożna sprawdzić, czy jeśli do aplika­ cji przekaże się prawidłowe dane, to aplikacja ta zwróci poprawne wyniki lub wygeneruje od­ powiednie skutki uboczne. Jeżeli istnieje taka możliwość, tworzymy im itacje zależności, aby m ieć pewność, że dany składnik jest testowany w izolacji. N a przykład test jednostkow y kon ­ trolera może im itow ać usługę, przy założeniu, że usługa ta działa prawidłowo. W teście jednostkow ym szukamy odpowiedzi na pytania typu: jeśli wszystkie inne składniki działają prawidłowo, to czy ten składnik też zadziała poprawnie? Systematycznie zapewniamy poprawność każdego elem entu, dzięki czemu podczas tropienia trudnego do wykrycia błędu m ożem y bardzo szybko odrzucić te części, których niezaw odność już potwierdziliśmy. T esty in teg racy jn e Testy jednostkow e są bardzo przydatne, szybkie i m ają niew ielkie rozm iary, ale nie są u n i­ wersalne. Przyjm uje się w nich pewne założenia dotyczące innych składników, ja k kontrolery i usługi. Przyjm ujem y, że działają one w określony sposób lub że zwracają określone in fo r­ m acje. Bardzo często założenia te są fałszywe. Testy integracyjne (w systemie AngularJS także tworzone przy użyciu narzędzi Karm a i Jasm ine) pozwalają sprawdzić, czy różne części apli­ kacji są prawidłowo skonfigurowane i połączone. W testach tych m ożna sprawdzić, czy kon­ troler kom unikuje się z usługą oraz czy usługa wywołuje odpowiednie efekty uboczne i zwraca prawidłowe wartości. Test tego rodzaju napisaliśmy w rozdziale 7., w punkcie „Testy integra­ cyjn e”, w celu przetestow ania integracji naszego kontrolera z usługą i upew nienia się, czy usługa wykonywała odpowiednie wywołania oraz zwracała właściwe wartości. W teście integracyjnym można przetestować interakcję między kontrolerem i usługą oraz spraw­ dzić konfigurację interceptorów. Ponadto można badać złożone interakcje przez sprawdzanie, co się dzieje, gdy jed na funkcja zostanie wywołane po innej, albo czy w wyniku sondowania co kilka sekund otrzym ujem y prawidłowe dane. Ale żądania X H R w testach integracyjnych są imitowane, co znaczy, że testy te nie kom unikują się z prawdziwym serwerem. Nawet ładowanie szablonu dla dyrektywy (jak w rozdziale 12.) trzeba imitować. Zatem w teście integracyjnym , choć m ożem y uzyskać pewność, że frontow a logika aplikacji działa prawidłowo, nadal jesteśm y uzależnieni od założenia, że nasz serwer poprawnie odpowiada na nasze żądania.

258

|

Rozdział 15. Porady i najlepsze praktyki

T esty scenariuszow e O s t a t n i r o d z a j t e s t ó w , j a k i p o w i n n i ś m y u t w o r z y ć d l a n a s z e j a p lik a c j i, t o t e s t y k o m p l e k s o w e , c z y l i s c e n a r iu s z o w e . T e o p i s a n e w r o z d z i a l e 14. t e s t y p o l e g a j ą n a u r u c h o m i e n i u p r z e g lą d a r k i, z a ł a d o w a n i u a p lik a c j i i k l i k a n i u w r ó ż n y c h m ie j s c a c h s t r o n y t a k , j a k b y t o r o b i ł u ż y t k o w n i k . S ą t o n a j b a r d z ie j m i a r o d a j n e te sty , k t ó r e p o z w a la j ą n a j le p ie j p r z e t e s t o w a ć d z ia ł a n ie a p lik a c j i. M i m o t o z a l e c a m y o s z c z ę d n e o p e r o w a n i e n i m i i n i e z a s t ę p o w a n ie n i m i w s z y s t k i c h i n n y c h r o ­ d z a j ó w t e s t ó w , p o n ie w a ż : • P o t r z e b a b a r d z o w i e l u t e s t ó w d o p o k r y c i a w s z y s t k i c h p r z y p a d k ó w , k t ó r e m o ż n a p o d z ie l ić i o b s ł u ż y ć z a p o m o c ą t e s t ó w j e d n o s t k o w y c h i in t e g r a c y j n y c h . • P o r a ż k a te stu s c e n a r iu s z o w e g o n ie k o n ie c z n ie d o s ta rc z a in f o r m a c j i p o t r z e b n y c h d o w y ­ k r y c i a ź r ó d ł a p r o b l e m u . W i a d o m o t y lk o , ż e c o ś n ie d z ia ł a t a k , j a k p o w i n n o . D o b r y z e s t a w t e s t ó w s c e n a r i u s z o w y c h je st n ie w ie lk i, s t a b iln y , o b l i c z a l n y i p o k r y w a w i ę k s z o ś ć p u n k t ó w in t e g r a c j i. C e l e m t y c h t e s t ó w je st z a p e w n ie n ie , ż e p r z y n a j m n ie j n a j b a r d z ie j p o d s t a ­ w o w e f u n k c j e a p lik a c j i s ą p o p r a w n i e p o ł ą c z o n e i p r a w i d ł o w o d z ia ła ją . P o w s z e c h n i e a k c e p t o w a n y m t y p o w y m p o d z i a ł e m i l o ś c i o w y m m i ę d z y te t r z y r o d z a j e t e s t ó w je st 7 0 : 2 0 : 10. O z n a c z a to , że 7 0 % t e s t ó w p o w i n n o b y ć t e s t a m i j e d n o s t k o w y m i , 2 0 % p o w i n n o b y ć te s t a m i in t e g r a c y j n y m i, a 1 0 % —

te sta m i k o m p le k s o w y m i. D o te g o n a le ż y d ążyć.

Kiedy wykonywać testy N a p i s a ł e ś ś w i e t n y , s o l i d n y z e s t a w t e s t ó w , k t ó r y w y k r y w a i e li m i n u j e w s z y s t k i e m o ż l i w e b łę d y . I c o d a le j ? K i e d y g o w y k o n y w a ć ? O t o k i l k a w s k a z ó w e k n a t e m a t n a j l e p s z y c h m o m e n t ó w w y k o ­ n y w a n ia testów :

Przy każdym zapisaniu pliku T e s t y j e d n o s t k o w e i in t e g r a c y j n e p o w i n n o s ię w y k o n y w a ć n ie r z a d z ie j n i ż z a k a ż d y m r a z e m , g d y z a p is z e s ię p l ik . T e s t y s c e n a r i u s z o w e m o g ą b y ć t r o c h ę z b y t r o z b u d o w a n e , ale t e s t y j e d ­ n o s t k o w e i in t e g ra c y jn e są

n ie s a m o w ic ie sz y b k ie ( j e ś li n ie , t o z n a c z y ż e s ą ź le n a p i s a n e ! ) .

K a r m a z a w ie r a f u n k c j ę o n a z w i e a u t o W a tc h , k t ó r a u m o ż l i w i a a u t o m a t y c z n e w y k o n y w a n i e t e ­ s t ó w , g d y z m i e n i s ię k o d ź r ó d ł o w y w p l i k u . N i e c o d a le j o p i s u j e m y z i n t e g r o w a n e ś r o d o w i s k o p r o g r a m i s t y c z n e W e b S t o r m , k t ó r e r ó w n i e ż je st z i n t e g r o w a n e z K a r m ą , d z i ę k i c z e m u m o ż e a u t o m a t y c z n i e w y k o n y w a ć t e s t y p o k a ż d y m z a p i s a n i u p l ik u . Z a le t ą t a k ie g o r o z w i ą z a n i a je st to, ż e n a t y c h m i a s t o t r z y m u j e m y in f o r m a c j e z w r o t n e . J e śli w ię c c o ś p ó j d z ie n ie t a k , m o ż n a p o p r o s t u c o fn ą ć z m ia n y i w r ó c ić d o p o p r z e d n ie g o sta n u .

Przed wysłaniem kod u do system u k o n tro li w ersji Je ż e li n i e u ż y w a s z ż a d n e g o s y s t e m u k o n t r o l i w e r s ji, t o n a t y c h m i a s t z a c z n ij . K a ż d y w i ę k s z y p r o j e k t ( w z a s a d z ie w s z y s t k o , c o n i e je st t w o r z o n e d l a z a b a w y ) p o w i n i e n b y ć p r z e c h o w y w a n y w r e p o z y t o r i u m s y s t e m u k o n t r o l i w e r s j i ( n p . G it ,

h ttp ://g it-s c m .c o m / , a lb o M e r c u r i a l —

h ttp ://m ercu rial.selen ic.co m / ). K o d w y s y ł a n y d o t a k ie g o r e p o z y t o r i u m p o w i n i e n b y ć p o p r a w ­ n y . W i ę k s z o ś ć t e g o t y p u s y s t e m ó w u d o s t ę p n i a p u n k t y z a c z e p ie n ia ( n p . G i t H o o k s

— h ttp ://

git-scm .com /book/en /C u stom izin g-G it-G it-H ooks ) u m o ż liw ia j ą c e w y k o n y w a n i e s k r y p t ó w i p o ­ le c e ń w o d p o w i e d z i n a o k r e ś l o n e z d a r z e n ia . N a j le p ie j je st w y k o n y w a ć t e s t y j e d n o s t k o w e , i n ­ t e g r a c y j n e i s c e n a r i u s z o w e p r z e d w y s ł a n i e m l u b z a t w ie r d z e n ie m k o d u , a b y d o b a z y d o d a w a n y b y ł t y lk o s p r a w d z a n y k o d .

Testowanie

| 259

C iągła in te g racja Ciągła integracja to koncepcja polegająca na obserwowaniu zm ian w systemie kontroli wersji i wykonywaniu testów na najnowszym kodzie przy każdym jego wysłaniu do repozytorium. W ten sposób wszelkie awarie i usterki wykrywa się natychm iast zamiast dopiero przy publi­ kowaniu nowej wersji zawierającej wiele zmian. Ciągła integracja pozwala powiązać problem z konkretną zmianą wprowadzoną w systemie. Zarówno Karma, jak i Protractor dobrze integrują się z większością otwartych systemów do ciągłej integracji, takich jak Jenkins (http://jenkins-ci.org/) i Travis (https://travis-ci.org/), więc jeśli dysponujesz systemem kom pilacji, włącz w nim wyko­ nywanie wszystkich swoich testów. Wszystkie wymienione czynności wystarczy wykonać raz. Później odpowiednie programy działają w tle bezobsługow o i pom agają w zapewnianiu wysokiej jakości kodu. Zrób więc, co trzeba, na początku, a potem ciesz się dobrymi wynikami.

Struktura projektu Jednym z najważniejszych i najczęściej zadawanych pytań dotyczących projektów AngularJS jest to, ja k powinna wyglądać struktura plików i folderów. Dotyczy to zarówno naszych własnych plików źródłow ych, ja k i im portow anych b ibliotek zew nętrznych, szablonów oraz plików częściow ych. Na początku tego podrozdziału prezentujem y kilka ogólnych porad na tem at najlepszych prak­ tyk, następnie przedstawiamy najlepsze sposoby budowania struktury katalogów i dołączania ze­ wnętrznych bibliotek do projektu, a na końcu podsuwamy kilka pomysłów, od czego zacząć pracę nad nowym projektem .

Najlepsze praktyki Treść tego punktu je st krótka i zwięzła, ale poruszone w nim tem aty rozw ijam y w następnych podrozdziałach. P otraktuj go jak o przew odnik albo odświeżenie w iadom ości, które m oże się przydać, gdy nie będziesz wiedział, co dalej robić. • W każdym pliku ogranicz się do jednej definicji kontrolera, usługi, dyrektywy lub filtru. Nie m ieszaj wielu składników w jednym wielkim pliku. • Nie twórz gigantycznych modułów. Lepiej podziel aplikację na m niejsze części. N iech główna aplikacja składa się z wielu m niejszych m odułów nadających się do wielokrotnego użytku. • M oduły i katalogi twórz z myślą o funkcjonalności (autoryzacja, adm inistracja, wyszukiwanie itd.), a nie o typie (kontrolery, usługi, dyrektywy). Dzięki tem u kod będzie łatwiejszy do wie­ lokrotnego użycia. • Używaj zalecanej składni wykorzystania funkcji modułów (a n g u la r.m o d u le('ja k iśM o d u ł'). ^ c o n tr o ll e r itd.) zamiast innych technik, które można spotkać w internecie i innych źródłach. • Używaj przestrzeni nazw. Dodawaj do nich moduły, kontrolery, usługi, dyrektywy i filtry. Na przy­ kład utwórz przestrzeń nazw mojafirma-wykres dla dyrektyw, mojProjektAutoryzacja itd. Dzięki tem u nowy program ista bez problem u odróżni Twój kod od rdzennego kodu AngularJS. • Przede wszystkim pisz spójnie. Ta rada nie dotyczy ściśle tylko systemu AngularJS, ale nazy­ waj pliki, foldery, dyrektywy i kontrolery zawsze według tych sam ych zasad.

260

I

Rozdział 15. Porady i najlepsze praktyki

Struktura katalogów W

t y m p u n k c i e in t e r e s u j e n a s t y l k o s t r u k t u r a k a t a l o g ó w a p l ik a c j i f r o n t o w e j , n ie s e r w e r o w e j .

A p l i k a c j a A n g u l a r J S m o ż e z a w i e r a ć s ię w j e d n y m f o l d e r z e s e r w o w a n y m s t a t y c z n i e z s e r w e r a . N a n a j w y ż s z y m p o z i o m i e o r g a n i z a c y j n y m p r o g r a m t a k i m o ż e z a w ie r a ć n a s t ę p u j ą c e f o ld e r y :

app F o ld e r

a p p s ł u ż y d o p r z e c h o w y w a n i a w s z y s t k i c h p l i k ó w J a v a S c r ip t , k t ó r e s a m i n a p i s z e m y .

D a le j j e s z c z e r o z w i n i e m y tę m y ś l.

tests F o l d e r d o p r z e c h o w y w a n i a t e s t ó w j e d n o s t k o w y c h i e w e n t u a l n ie k o m p l e k s o w y c h t e s t ó w s c e ­ n a r iu s z o w y c h .

d a ta W

f o ld e r z e t y m m o ż n a p r z e c h o w y w a ć w s z y s t k ie n i e d y n a m i c z n e d a n e w y k o r z y s t y w a n e w s p ó l ­

n ie p r z e z r ó ż n e s k ł a d n i k i a p lik a c ji.

scritps F o ld e r d o p r z e c h o w y w a n ia s k r y p t ó w b u d o w y i i n n y c h czę sto p o t r z e b n y c h s k r y p t ó w .

In n e pliki P o z o s t a ł e p l i k i , k t ó r e n i e m u s z ą b y ć w o s o b n y m k a t a lo g u , n p .

p a c k a g e.jso n , bow er.json i in n e ,

m o ż n a z a p is a ć w f o ld e r z e g ł ó w n y m . Z a n im

p r z e jd z ie m y d o s z c z e g ó ło w e g o o p is u z a w a r to śc i f o ld e r ó w

a p p c z y tests , n a j p i e r w z d e f i­

n i u j e m y p e w n e o g ó l n e p o ję c ia :

G rupow anie w edług fu n k cjo n a ln o ści lu b k om p on en tu W

w i e l u p r z y k ł a d a c h i s t a r t o w y c h p r o j e k t a c h A n g u l a r J S s u g e r o w a n a je st t a k a s t r u k t u r a k a ­

t a lo g ó w , w k t ó r e j u t w o r z o n y je st o s o b n y f o ld e r n a w s z y s t k ie k o n t r o l e r y , o s o b n y n a u s ł u g i itd. Z d o ś w ia d c z e n i a w ie m y , że t a k a b u d o w a s t a n o w i d la r o z r a s t a j ą c e g o się p r o j e k t u p o w a ż n e o g r a ­ n ic z e n ie . D l a t e g o z a l e c a m y g r u p o w a n i e k o d u w f o l d e r a c h w e d ł u g k o m p o n e n t ó w l u b f u n k ­ c j o n a ln o ś c i. N a p r z y k ł a d k a ż d y z k o m p o n e n t ó w lo g o w a n i a , a u t o r y z a c j i i w y s z u k i w a n i a m o ż e m i e ć o s o b n y f o ld e r . W i d o k i i p o d s e k c j e a p lik a c j i r ó w n i e ż m o ż n a g r u p o w a ć w f o ld e r a c h , n p .

a d m in i view , w k t ó r y c h m o g ą b y ć d a ls z e p o d f o l d e r y , j a k se a rc h , list itd . K o m p o n en ty i sekcje ap lik acji P la n u j ą c s t r u k t u r ę a p lik a c j i, c z ę s to w y k o r z y s t u j e m y d w ie k o n c e p c j e . P i e r w s z a t o k o m p o n e n t y , c z y li s t o s o w a n e w a p lik a c j i w id ż e t y w ie lo k r o t n e g o u ż y t k u , k t ó r e n ie s ą z w ią z a n e z ż a d n ą k o n ­ k r e t n ą s t r o n ą a n i s e k c j ą in t e r f e j s u u ż y t k o w n i k a . K o m p o n e n t a m i t a k i m i m o g ą b y ć e le m e n t w y b o r u d a t y a lb o u s łu g a a u t o ry z a c ji —

u ż y w a n e w w i e l u r ó ż n y c h a p lik a c j a c h . W

f o ld e r z e

com p on en ts p o w i n n y z n a j d o w a ć s ię p o d f o l d e r y d o p r z e c h o w y w a n i a r ó ż n y c h s k ł a d n i k ó w : • P o w i ą z a n e u s łu g i, d y r e k t y w y i s p o k r e w n i o n e p lik i. • P o t r z e b n e i n f o r m a c j e i z a s o b y , j a k a r k u s z e s t y l ó w , o b r a z y i in n e , m o ż n a z a p i s a ć w t y m f o ld e r z e b ą d ź o s o b n o , w z a l e ż n o ś c i o d p o t r z e b . • Je śli k o m p o n e n t je st s k o m p l i k o w a n y , k a ż d y f o ld e r m o ż n a p o d z ie l ić n a k o le j n e p o d f o l d e r y .

Struktura projektu

| 261

D r u g a k o n c e p c j a t o s e k c j e a p lik a c j i, k t ó r e o d z w i e r c i e d l a j ą jej s t r u k t u r ę . M o g ą t o b y ć t r a s y , s t r o n y (n p . w y s z u k i w a n i a , z lis t ą e le m e n t ó w , a d m i n i s t r a c y j n a it d .), a n a w e t m n ie j s z e p o d s e k ­ cje s t r o n y ( n p . p u l p it ) . F o l d e r y s e k c j i z r e g u ły : • O d z w ie r c ie d la j ą w id o k i p r z e d s t a w ia n e u ż y t k o w n ik o w i. • Z a w i e r a j ą t y l k o s z a b l o n y H T M L (i C S S ) o r a z p r a c u j ą c y z n i m i k o n t r o l e r . K o m p o n e n t l u b s e k c j a ( a lb o p o d k o m p o n e n t b ą d ź p o d s e k c j a ) m o ż e m i e ć w ł a s n ą d e f in ic j ę m o d u ł u . D e c y z j a , c z y d e f in ic j a t a k a je st w d a n y m p r z y p a d k u p o t r z e b n a , p o w i n n a b y ć p o d j ę t a n a p o d s t a w ie te g o , c z y m o d u ł je st p r z e z n a c z o n y d o w ie lo k r o t n e g o u ż y t k u lu b s e le k t y w n e g o d o ł ą c z a n i a d o r ó ż n y c h a p lik a c j i.

T esty pow inny być lu strzany m od biciem ap lik acji F o ld e r

tests p o w i n i e n z a w ie r a ć g ł ó w n i e k o m p l e k s o w e scenariuszow e testy t w o r z o n e p r z y

u ż y c iu n a r z ę d z ia P ro tra c to r. S t r u k t u r a w e w n ę t rz n a te g o fo ld e r u p o w in n a n a ś la d o w a ć n a s z ą a p lik a c j ę i jej w i d o k i . W

t y m s e n s ie k a ż d y w i d o k i k a ż d a s e k c ja s t r o n y p o d d a w a n e j t e s t o m

w d z ia ła ją c e j n a ż y w o a p lik a c j i m o g ł y b y m ie ć p o d f o l d e r z w ł a s n y m i t e s t a m i. S t r u k t u r a f o ld e r ó w w t y m p r z y p a d k u o d z w i e r c i e d l a ł a b y s t r u k t u r ę a p lik a c j i, a n ie b y ł a d o p a s o w a n a d o is tn ie ją c e j s t r u k t u r y k a t a lo g ó w . N a t o m ia st

testy jed n ostk o w e s ą p r z e c h o w y w a n e w r a z z k o d e m a p lik a c j i w p o d f o l d e r a c h f o l ­

a p p . D z i ę k i t e m u t e s t y j e d n o s t k o w e w s z y s t k i c h k o n t r o l e r ó w , u s łu g , d y r e k t y w i f i l t r ó w

d e ru

z n a j d u j ą s ię w t y m s a m y m f o ld e r z e c o t e s t o w a n y p r z e z n ie s k ł a d n i k . U ł a t w i a t o z n a j d o w a n ie t e s t ó w i z a r z ą d z a n ie n i m i o r a z p o z w a la n a r z ę d z i o m w d r o ż e n i o w y m w y k l u c z y ć t e s t y z g o t o w e j p a c z k i p ro g ra m o w e j.

K on w en cje nazew nicze N a p o c z ą t e k w a r t o p r z e c z y t a ć p o d r ę c z n i k s t y l i s t y c z n y d o J a v a S c r ip t u f i r m y G o o g l e ( h ttp ://

google-stylegu ide.googlecode.com /svn /tru n k/javascriptgu ide.xm i ) , n a t o m ia s t p o t e m m o ż n a s k o ­ r z y s t a ć z d o d a t k o w y c h z a le c e ń d o t y c z ą c y c h A n g u l a r J S , a k o n k r e t n ie : • N a z w y p l i k ó w p o w i n n y b y ć w y s t a r c z a j ą c o t r e ś c iw e , a b y d a ło s ię p o z n a ć , d o c z e g o d a n y p l i k n a l e ż y i j a k i e g o r o d z a j u o b i e k t A n g u l a r J S r e p r e z e n t u j e . N a p r z y k ł a d j e ś li w a p lik a c j i je st s e k c ja o n a z w ie

a d m in sea r c h , t o f o ld e r m o ż e n a z y w a ć s ię a d m in sec tio n , a z n a j d u j ą c y

s ię w n i m k o n t r o l e r —

ad m in sectio n -co n tro ller.js .

• N a jle p ie j, jeżeli n a z w a p l i k u k o n t r o l e r a k o ń c z y się p r z y r o s t k ie m r o s t k ie m

-controller.js , p l i k u u s ł u g i p r z y ­

-service.js , p l i k u d y r e k t y w y p r z y r o s t k i e m -directive.js , a f ilt r u p r z y r o s t k ie m -filter.js .

• N a z w y p l ik ó w z a w ie ra ją c y c h te sty p o w i n n y b y ć z a k o ń c z o n e p r z y r o s t k ie m d y r e k t y w a e le m e n t u d o w y b o r u d a t y m o ż e m ie ć n a z w ę p l ik u te st m o ż e z n a j d o w a ć s ię w p l i k u o n a z w i e • W

-test.js . Z a t e m

d a te p ic k er -d ir ec tiv e.js , a jej

d atep icker-d irectiv e_ test.js .

n a z w a c h p l i k ó w le p ie j j e s t u ż y w a ć m a ł y c h lit e r n i ż n o t a c j i w ie lb ł ą d z ie j l u b z a c z y n a ć

k a ż d y w y r a z o d d u ż e j lit e ry . T e r a z z o b a c z m y , j a k te w s z y s t k ie z a s a d y m o ż n a z a s t o s o w a ć w p r a k t y c e . P o n iż e j z n a j d u j e s ię p r z y ­ k ł a d s t r u k t u r y p r o j e k t u p r o s t e j a p lik a c j i t y p u C R U D :

262

|

Rozdział 15. Porady i najlepsze praktyki

. ap p • app.css • app.js • in d ex .h tm l • com p on en ts // k o m p o n e n t y w i e l o k r o t n e g o u ż y t k u • d a tep ick er • d atep icker-d irectiv e.js • d atep icker-d irectiv e_ test.js • au th o riz a tio n • a u t h o r iz a t io n s • a u th orization -serv ice.js • a u th o r iz a tio n -s e r v ic e te s tjs • ui-w idgets • ui-w idgets.js • g rid • g rid .h tm l • grid -d irectiv e.js • g r id -d ir e c tiv e te s t.js • d ia lo g • dialog-service.js • d ia lo g -se rv ic etes t.js • list • list.htm l • list.css • list-controller.js • list-co n tro lle rtes t.js • login • login .htm l • login -con troller.js • search • search .h tm l • search.css • search -con troller.js • search -con troller_ test.js

Struktura projektu

| 263

• d etail • d eta il.h tm l • d e t a il - c o n t r o l le r . j s • d e ta il-c o n tr o lle r te s t.js • a d m in • cre a te • create.h tm l • create-con troller.js • create-con troller_ test.js • u p d a te • u p d ate.h tm l • u p d ate-con troller.js • u p d a te -c o n tr o lle r te s t.js • ven dors // z e w n ę t r z n e z a le ż n o ś c i • u n derscore • jq u e r y • bootstrap • e2 e // t e s t y k o m p l e k s o w e • ru n n er.htm l • login _scen ario.js • lis ts c e n a r io .js • search _ scen ario.js • d etail_ scen ario.js • ad m in • a d m in _ crea te_ sc en a rio .js • a d m in _ u p d a te_ scen a rio.js

Biblioteki zewnętrzne W i e s z ju ż , j a k m o ż e w y g l ą d a ć s t r u k t u r a T w o j e j a p lik a c j i i jej k o d u ź r ó d ł o w e g o , ale c o z z e w n ę t r z ­ n y m i z a l e ż n o ś c i a m i i b i b l io t e k a m i , b e z k t ó r y c h n ie s p o s ó b s o b ie p o r a d z i ć ? Z a l e c a m y u t w o r z e n ie o g ó l n e g o f o ld e r u o n a z w ie k ic h t a k ic h d o d a t k ó w —

vendors w g ł ó w n y m f o ld e r z e a p lik a c j i i p r z e c h o w y w a n ie w n i m w s z y s t ­

le c z t o t y l k o j e d n a s t r o n a m e d a lu .

A b y u ł a t w i ć s o b i e z a p a n o w a n i e n a d a k t u a li z a c j a m i i r ó ż n y m i w e r s j a m i z e w n ę t r z n y c h z a le ż n o ś c i, z a le c a m y s k o r z y s t a n ie z n a r z ę d z ia t y p u B o w e r

(h ttp ://w w w .bo w er.io /), c z y l i m e n e d ż e r a p a k i e t ó w

d la z a le ż n o ś c i s ie c io w y c h . P r a w ie k a ż d a z e w n ę t r z n a z a le ż n o ś ć u ż y w a n a w p r o j e k c ie A n g u l a r J S je st d o s t ę p n a ta k ż e w p o s t a c i p a k ie t u B o w e r.

264

I

Rozdział 15. Porady i najlepsze praktyki

Instalacja i wdrożenie menedżera Bower do użytku wymaga wykonania tylko trzech prostych czynności: 1. Zainstaluj narzędzie Bower — jest to pakiet N odeJS, więc wystarczy wykonać polecenie npm in s t a ll bower. 2. Utwórz listę swoich zależności w pliku definicji pakietów Bower (najczęściej o nazwie bow er.json). W pliku tym należy określić wersje bibliotek używanych w projekcie. 3. W ykonaj polecenie bower in sta l l (albo bower update) w dowolnym projekcie, aby pobrać naj­ nowsze zależności do folderu tego projektu i włączyć automatyczną obsługę wersji programów. W czym pomaga narzędzie Bower: • Zwalnia programistę z konieczności wprowadzania zewnętrznych bibliotek do bazy kodu. • Ułatwia zarządzanie aktualizacjami zewnętrznych bibliotek, które sprowadza się do zmiany num eru w jednym pliku JSON. • Ułatwia każdemu now emu program iście przystępującemu do projektu sprawdzenie, jakie zewnętrzne biblioteki są używane. Krótko mówiąc, Bower ułatwia integrację zewnętrznych bibliotek z projektem. System AngularJS też jest dostępny w postaci pakietu Bower, więc koniecznie go wypróbuj w swojej następnej aplikacji!

Punkt początkowy Przedstawiliśmy nasze zdanie na tem at tego, ja k powinna wyglądać struktura plików i katalogów projektu AngularJS i ja k powinno się nazywać pliki w takim projekcie, oraz opisaliśmy kilka do­ brych praktyk. Ale nie musisz tego wszystkiego robić samodzielnie od zera. W spaniała społecz­ ność skupiona wokół projektu AngularJS stworzyła wiele przydatnych narzędzi i bibliotek, które m ożna wykorzystać jako bazę do tworzenia własnego projektu. Poniżej prezentujem y kilka p o­ mysłów, od czego zacząć pracę nad nowym projektem : Y eo m an Yeoman to menadżer zadań automatyzujący wiele rutynowych czynności, których nie da się unik­ nąć w żadnym projekcie. Nie jest to narzędzie przeznaczone wyłącznie dla systemu AngularJS, ale ma dla niego wtyczkę. Sam system AngularJS uwalnia programistę od konieczności pisania sza­ blonowego kodu, lecz nie napisze za nas tras, nie doda kodu H TM L i kontrolera, nie utworzy szkieletu dla testu jednostkowego itd. Natomiast Yeoman idzie o krok dalej i automatyzuje właśnie te czynności. Wystarczy napisać polecenie w stylu yo angul a r:ro u te moj aNowaTrasa, aby wygene­ rować plik route.htm l, dodać szkielet kontrolera i testu, dodać kod JavaScript do pliku index.htm l itd. Istnieje kilka generatorów AngularJS, różniących się składnią i strukturą folderów, więc jest duża szansa, że znajdziesz coś odpowiadającego Twoim potrzebom. Yeoman zapewnia także skrypty kompilacji i zadania Grunt, które mogą być potrzebne w średnich i dużych przedsięwzięciach. P ro jek ty początkow e A n g u larJS Jest wiele projektów początkowych w AngularJS, które zapewniają podstawową strukturę kata­ logów, szkielet aplikacji i skrypty kom pilacyjne. Do najczęściej używanych i najlepiej znanych zaliczają się n g -b o ile rp la te , i angular-seed. O ba dostarczają podstawową strukturę i zadania Grunt, które są potrzebne do rozpoczęcia pracy.

Struktura projektu

| 265

M ean .io A jeśli działasz w branży rozwiązań kom pleksowych i nie masz nic przeciwko wykorzystaniu N odeJS jak o serw era, to m oże zainteresow ać Cię p ro jek t M ean .IO ( h ttp ://w w w .m e a n .io /). Zdobywający ostatnio dużą popularność akronim M EA N oznacza M ongoD B, Express, AngularJS i NodeJS. M ean.IO to gotowy szkielet projektu aplikacji wyposażony w odpowiednią strukturę folderów, skrypty kom pilacyjne itd. C hoć narzuca pewien rodzaj budowy aplikacji i paradygmat pracy, to są one bardzo dobrze przemyślane i warto dać im szansę.

Budowanie projektu Opisaliśm y strukturę folderów projektu, konw encje nazewnicze, a nawet kwestie dotyczące uru­ cham iania projektów A ngularJS. N iektóre m echanizm y rozruchow e są zaopatrzone w skrypty budow y i zadania G runt, ale i tak należy wiedzieć, na czym polega budow a projektu AngularJS oraz co jest najważniejsze przy tworzeniu takiego projektu.

Grunt G runt to w zasadzie standardowe narzędzie, jeśli chodzi o skrypty do budow y projektów Java­ Script. Ten napisany przy użyciu JavaScriptu i N odeJS m echanizm do urucham iania zadań m a wiele wtyczek pozwalających zautomatyzować różne etapy budowy projektu, a konkretnie: • łączenie plików; • kopiowanie i przenoszenie plików; • zm ienianie nazw plików; • m inim alizację plików CSS; • m inim alizację plików JavaScript; • wykonywanie testów i skryptów powłoki. Praca z narzędziem Grunt polega na utworzeniu pliku JavaScript im portującego wszystkie p o­ trzebne wtyczki, a następnie na zdefiniowaniu za pom ocą JavaScriptu własnych zadań i czynności do wykonania dla określonych zadań narzędzia Grunt. Na przykład m ożna zdefiniować zadanie budowy wykonujące następujące czynności: • uruchom ienie testów K arm a i P rotractor w celu zweryfikowania stanu aplikacji; • połączenie wielu plików JavaScript w jeden; • uruchom ienie kom pilatora JavaScript w celu usunięcia spacji i zredukowania rozm iaru kodu JavaScript; • skompilowanie kodu CSS do m niejszej postaci; • przeniesienie plików H TM L, JavaScript i CSS do osobnego katalogu w ramach procesu wdrażania. Innym zadaniem m oże być włączanie wszystkich testów jednostkow ych i integracyjnych wyko­ rzystujących tylko Karm ę itd. Narzędzie Grunt m a wiele opcji konfiguracyjnych, dzięki którym za jego pom ocą m ożna stworzyć taki proces, jakiego się potrzebuje.

266

|

Rozdział 15. Porady i najlepsze praktyki

Serwowanie pojedynczego pliku JavaScript U ż y w a j ą c n a r z ę d z i a G r u n t (a t a k ż e k a ż d e g o i n n e g o n a r z ę d z ia t e g o t y p u ) , m u s i m y p a m ię t a ć o k i l k u r z e c z a c h i z a d b a ć o to, b y p o d c z a s p r o c e s u b u d o w y z o s t a ł y w y k o n a n e p e w n e c z y n n o ś c i. P r z e d e w s z y s t k i m n a l e ż y z w r ó c i ć u w a g ę n a p l i k i J a v a S c r ip t (a k o n k r e t n ie n a i c h lic z b ę ) d o d a w a n e d o p l i k u

index.htm l ( lu b j a k i e g o k o lw i e k in n e g o ) . P o d c z a s p r a c y n a d a p lik a c j ą A n g u l a r J S lep ie j je st p o s ł u g iw a ć s ię w i e l o m a p l i k a m i i f o ld e r a m i, p o n i e w a ż u ł a t w i a t o z a p a n o w a n i e n a d p r o j e k t e m i j e g o d e b u g o w a n ie . A l e d o ł ą c z a n i e 2 5 0 p l i k ó w J a v a S c r ip t d o a p lik a c j i p r o d u k c y j n e j to i d e a l n y p r z e p i s n a p o ­ w o l n ą s t r o n ę in t e r n e t o w ą . P r z e g l ą d a r k i m a j ą w b u d o w a n e o g r a n i c z e n i a c o d o l i c z b y r ó w n o c z e ś n i e w y s y ł a n y c h ż ą d a ń GET d o j e d n e j d o m e n y . P o w i e d z m y n p . , ż e p r z e g l ą d a r k a m o ż e p o b r a ć n a r a z t y l k o 8 p l i k ó w , a w ię c 2 5 0 p l i k ó w z o s t a n ie p o b r a n y c h w p a r t i a c h p o 8, c o p r z e c ię t n e m u u ż y t k o w n i k o w i k o m p u t e r a z a j m ie w ie c z n o ś ć . Z a l e c a n y m s p o s o b e m s e r w o w a n i a p l i k ó w J a v a S c r i p t w a p l i k a c j a c h p r o d u k c y j n y c h j e s t ł ą c z e n ie i c h w j e d e n d u ż y p l i k . D z i ę k i t e m u g d y p r z e g l ą d a r k a z a ż ą d a p l i k ó w J a v a S c r ip t a p lik a c j i, z o s t a n ą o n e p r z e s ł a n e w j e d n y m ż ą d a n i u , a n i e w 2 5 0 c z y c h o ć b y 50. Je ż e li a p lik a c j a je st s e r w o w a n a w in t r a n e c ie , t o w s z y s t k ie p l i k i J a v a S c r ip t ( w lic z a j ą c z a le ż n o ś c i z e w n ę t r z n e ) b ę d ą s e r w o w a n e t y l k o p r z e z tę a p lik a c j ę , w ię c m o ż n a p o k u s i ć s ię o d o ł ą c z e n i e d o j e d n e g o w i e l k i e g o p l i k u t a k ż e z e w n ę t r z n y c h b ib lio t e k . Z d r u g ie j s t r o n y z a l e ż n o ś c i z e w n ę t r z n e c z ę s t o d o ł ą c z a s ię z s ie c i d o s t a r c z a n ia t r e ś c i ( a n g .

con ten t

delivery netw ork — C D N ) z a m ia s t z w ł a s n y c h z a s o b ó w . D z i ę k i t e m u je ś li u ż y t k o w n i k j u ż w c z e ś n ie j p o b r a ł B o o t S t r a p z C D N p o d c z a s p r z e g l ą d a n i a in n e j w i t r y n y in t e r n e t o w e j , to p o w e j ś c iu n a n a s z ą s t r o n ę n ie b ę d z ie m u s i a ł p o b ie r a ć te g o s k r y p t u p o r a z d r u g i, p o n ie w a ż p r z e g lą d a r k a z a p is z e g o w s w o ­ jej p a m i ę c i p o d r ę c z n e j . D w i e ś w ie t n e s ie c i C D N d la b ib l io t e k J a v a S c r ip t t o G o o g l e H o s t e d L ib r a r ie s

(h ttp s://d ev elop ers.g o o g le.co m /sp eed /libra ries/) i C D N J S (h ttp ://cd n js.co m /).W n i c h p o w i n n o s ię u d a ć z n a le ź ć w i ę k s z o ś ć b i b li o t e k , k t ó r e b ę d ą C i p o t r z e b n e . P r z y o k a z j i ł ą c z y ć m o ż n a n i e t y lk o p l i k i J a v a S c r ip t , ale i C S S . N a l e ż y d ą ż y ć d o te g o , a b y a p lik a c j a p o d c z a s ł a d o w a n i a n i e w y k o n y w a ł a w ię c e j n i ż 3 - 5 r ó w n o c z e s n y c h ż ą d a ń . D z i ę k i t e m u u n i k a się z b lo k o w a n ia p r z e g lą d a r k i i s z e r e g o w a n ia p r z e z n ią ż ą d a ń .

Minimalizacja P o d c z a s g d y łą c z e n ie p l i k ó w J a v a S c r ip t w j e d e n d u ż y p l i k r e d u k u j e lic z b ę ż ą d a ń w y s y ł a n y c h p r z e z p r z e g lą d a r k ę d o s e r w e r a , n i e m a o n o w p ł y w u n a o g ó l n ą i l o ś ć d a n y c h , j a k ą t r z e b a p r z e s ł a ć p r z e z in t e r n e t . A b y a p lik a c j a b y ł a s z y b k a j a k b ł y s k a w i c a , n a l e ż y te ż z a d b a ć o tę d r u g ą k w e s tię . P l i k i J a v a S c r ip t i C S S p o w i n n o się łą c z y ć w p o j e d y n c z e d u ż e p lik i, k t ó r e z k o le i p o w i n n o się p o d d a w a ć p r o c e s o w i m i n i m a l iz a c j i w t a k ic h n a r z ę d z i a c h j a k n p . U g l i f y J S c z y C lo s u r e C o m p ile r f ir m y G o o g le

(https://github.com /m ishoo/U glifyJS)

(https://developers.google.com /closu re/com piler/). T e d w a k o m ­

p il a t o r y J a v a S c r i p t u p r z e g lą d a j ą p l i k J a v a S c r ip t i u s u w a j ą z n i e g o w s z y s t k ie n ie p o t r z e b n e r z e c z y , t a k ie j a k s p a c je i k o m e n t a r z e , w t e n s p o s ó b z m n ie j s z a j ą c j e g o o b j ę t o ś ć . P o n a d t o k o m p i l a t o r m o ż e te ż s k r a c a ć n a z w y z m i e n n y c h i f u n k c j i, p o n i e w a ż d la p r z e g lą d a r k i n ie m a j ą o n e ż a d n e g o z n a c z e n ia .

Budowanie projektu

| 267

P r z y p o m i n a m y , że j e ś li p l a n u j e s z m i n i m a l i z o w a ć p l i k i, m u s i s z s t o s o w a ć b e z p i e c z n y s t y l w s t r z y ­ k i w a n i a z a le ż n o ś c i. J e ż e li o t y m z a p o m n i s z , a p lik a c j a m o ż e p r z e s t a ć d z ia ła ć . P o n a d t o m o ż n a u ż y ć o t w a r t e j b i b l i o t e k i n g - a n n o t a t e ( h ttp s://g ith u b .com /o lo v /n g -an n o ta te ) , k o n w e r t u j ą c e j k o d n ie n a d a j ą c y s ię d o m i n i m a l i z a c j i n a k o d n a d a j ą c y s ię d o te g o . P o d o b n i e d z ia ła j ą k o m p i l a t o r y C S S , tzn . t a k ż e u s u w a j ą s p a c je i k o m e n t a r z e o r a z m o g ą p r z e p is y w a ć a r k u s z e s t y l ó w w b a r d z ie j e f e k t y w n y s p o s ó b . M i n i m a l i z a c j a p o w i n n a b y ć w y k o n y w a n a w k a ż d e j a p lik a c j i.

Zadanie ng-templates Z a d a n i e n a r z ę d z i a G r u n t d o u ż y t k u w in t e r n e c i e n a z y w a s ię n g - t e m p l a t e s

( h ttp s://g ith u b .c o m /

er icclem m o n s/g ru n t-a n g u la r-tem p la tes ). U m o ż l i w i a o n o u p r z e d n i e z a ł a d o w a n i e w s z y s t k i c h p o ­ t r z e b n y c h w a p lik a c j i s z a b l o n ó w H T M L , z a m i a s t w y k o n y w a ć ż ą d a n i a X H R w c e lu i c h p o b r a n i a d o p i e r o w t e d y , g d y s ą p o t r z e b n e . Z a d a n i e n g - t e m p l a t e s w c z y t u j e w s z y s t k ie p l i k i H T M L i g e n e ­ r u j e p l i k A n g u l a r J S J a v a S c r ip t z a w ie r a j ą c y c a ły k o d H T M L w b u f o r z e t e m p la t e C a c h e p o d c z a s ł a ­ d o w a n i a a p lik a c j i. Z a d a n i e b u d o w y n g - t e m p l a t e s d o s k o n a l e s p r a w d z a s ię w p r z y p a d k u n ie w ie lk ie j l i c z b y s z a b l o n ó w lu b k ie d y u z a s a d n io n e je st w c z y t a n ie z g ó r y w s z y s t k ic h s z a b lo n ó w , a b y p r z y s p ie s z y ć d z ia ła n ie p r o g r a m u . A l e j e ś li s z a b l o n ó w je st d u ż o , m o ż n a w y b r a ć d o z b u f o r o w a n i a t y l k o n a j c z ę ś c ie j u ż y ­ w a n e , a p o z o st a łe ła d o w a ć a s y n c h r o n ic z n ie .

Najlepsze praktyki O m ó w i l i ś m y j u ż t e m a t y s t r u k t u r y f o l d e r ó w i m e t o d b u d o w y p r o j e k t ó w , w ię c t e r a z z a j m i e m y się n a j l e p s z y m i p r a k t y k a m i t w o r z e n i a a p lik a c j i A n g u l a r J S . S ą t o r z e c z y , k t ó r e n a l e ż y r o b i ć l u b k t ó ­ r y c h n a l e ż y u n i k a ć j a k o g n i a , a b y n i e o c z e k i w a n i e n i e z n a l e ź ć s ię w n i e z w y k l e t r u d n e j s y t u a c j i. N i e k t ó r e p o r a d y w y n i k a j ą p o p r o s t u z e z d r o w e g o r o z s ą d k u , a i n n e s ą b a r d z o s p e c y f i c z n e , ale w s z y s t k ie w a r t o p a m ię t a ć .

Uwagi ogólne P o n i ż e j p r z e d s t a w i a m y k i l k a o g ó l n y c h p o r a d , o k t ó r y c h w a r t o p a m i ę t a ć p r z y p i s a n i u a p lik a c j i A n g u la r J S : • M a ł e p l i k i s ą le p s z e o d d u ż y c h . O w ie le ła t w ie j n a d n i m i z a p a n o w a ć , z n a le ź ć w n i c h e w e n t u ­ a ln e b ł ę d y i je z r o z u m i e ć . P r z y j m u j e się , ż e d u ż y p l i k t o t a k i, k t ó r y z a w i e r a w ię c e j n i ż 1 0 0 w ie r s z y k o d u . • U ż y w a j f u n k c j i s e t T im e o u t s y s t e m u A n g u l a r J S , k t ó r a je st u s ł u g ą $ t im e o u t , i f u n k c j i s e t I n t e r v a l s y s t e m u A n g u l a r J S , k t ó r a je st u s ł u g ą $ i n t e r v a l . W r a z ie p o t r z e b y ła t w o je i m i t o w a ć w t e s t a c h j e d n o s t k o w y c h i n i e t r z e b a c z e k a ć n a z a k o ń c z e n i u o d s t ę p u c z a s o w e g o , a b y w y k o n a ć test. M o ż n a n a w e t u s t a w ić lic z b ę w y w o ł a ń f u n k c j i in t e r w a ł o w e j w s w o i c h te s t a c h . D l a t e g o u ż y w a j w e r s j i A n g u l a r J S t y c h f u n k c j i.

268

|

Rozdział 15. Porady i najlepsze praktyki

• J e śli w k o n t r o l e r z e l u b d y r e k t y w ie w y k o r z y s t a n a z o s t a n ie u s ł u g a $ t im e o u t l u b $ i n t e r v a l , to n a l e ż y j ą s k a s o w a ć b ą d ź a n u l o w a ć p o jej z n is z c z e n iu , ż e b y n ie p o t r z e b n ie n ie d z ia ł a ła w tle. • J e ż e li d o d a j e s z p r o c e d u r y n a s ł u c h o w e p o z a z a s i ę g i e m A n g u l a r J S , z a d b a j o i c h p r a w i d ł o w e s k a s o w a n i e . S y s t e m A n g u l a r J S u t r z y m u j e w ł a s n e z a k r e s y i p r o c e d u r y n a s ł u c h o w e , ale w s z y s t ­ k o , c o p r o g r a m i s t a d o d a s a m o d z i e l n ie , m o ż e w y m a g a ć s p e c j a l n e g o p o t r a k t o w a n ia . C z y n n o ś c i z t y m z w ią z a n e m o ż n a w y k o n a ć p o d c z a s n is z c z e n ia z a k r e s u p r z e z d o d a n ie p r o c e d u r y n a s łu ­ c h o w e j $ s c o p e . $ o n ( '$ d e s t r o y ',

fu n c t io n ()

{}).

• S ta r a j s ię u n i k a ć g ł ę b o k i c h o b s e r w a c j i ( $ s c o p e . $ w a t c h ( ) z t r z e c im a r g u m e n t e m u s t a w i o n y m n a t r u e ) . J e st t o c z a s o c h ł o n n a c z y n n o ś ć , k t ó r e j n a d u ż y w a n i e m o ż e p o g o r s z y ć w y d a j n o ś ć a p lik a c j i . Z a m i a s t t e g o le p ie j j e s t u t w o r z y ć p r o s t ą z m i e n n ą l o g i c z n ą s y g n a l i z u j ą c ą z m i a n y o b i e k t u i o b s e r w o w a ć tę z m ie n n ą . • S ta r a j s ię p o s t ę p o w a ć z g o d n i e z p r z y j ę t y m w A n g u l a r J S m o d e l o w y m p a r a d y g m a t e m p r o g r a ­ m o w a n i a . M o d e l i d a n e p o w i n n y d e c y d o w a ć o s t a n i e i n t e r f e j s u u ż y t k o w n i k a , a j e ś li t r z e b a z a k t u a l i z o w a ć t e n in t e r f e j s , w y s t a r c z y t y l k o z a k t u a l i z o w a ć m o d e l . W

r e a k c j i n a t o in t e r f e j s

u ż y t k o w n i k a p o w i n i e n z a k t u a l i z o w a ć s ię a u t o m a t y c z n ie . • Je ż e li c h c e s z w y k o n a ć p e w n e z a d a n i a z a k a ż d y m r a z e m , g d y s e r w e r z w r ó c i b ł ą d a u t o r y z a c j i a lb o 4 0 4 , u ż y w a j i n t e r c e p t o r ó w H T T P . N i e c h t y lk o u s ł u g i i k o n t r o l e r y o b s ł u g u j ą k o n k r e t n e r o d z a j e b łę d ó w .

Uwagi dotyczące usług U s ł u g i m o ż n a s t o s o w a ć d o t w o r z e n ia w s p ó l n y c h in t e r f e j s ó w A P I i m a g a z y n ó w n a d a n e d la całej a p lik a c j i. O t o k i l k a w s k a z ó w e k , j a k i c h n a j le p ie j u ż y w a ć : • U s ł u g i s ą s i n g l e t o n a m i . W y k o r z y s t a j t e n f a k t d o u t w o r z e n i a in t e r f e j s u A P I , m a g a z y n u n a d a ­ n e a l b o b u f o r a . W e w s z y s t k i c h t y c h z a s t o s o w a n i a c h u s ł u g i s p r a w d z a j ą s ię d o s k o n a l e . • J e śli c h c e s z u t r z y m y w a ć w a p li k a c j i w s p ó l n y s t a n , t o r o z w a ż m o ż l i w o ś ć u t w o r z e n i a u s łu g i. • U s ł u g i , f a b r y k i i d o s t a w c y n ie r ó ż n i ą s ię m i ę d z y s o b ą p o d w z g l ę d e m w y d a j n o ś c i. W s z y s t k i e te k o n s t r u k c j e im p l e m e n t u j e s ię t a k s a m o , w ię c w y b i e r z tę, k t ó r a n a j b a r d z ie j o d p o w i a d a T w o ­ j e m u s t y l o w i p r o g r a m o w a n i a i T w o i m p o t r z e b o m , a p o t e m k o n s e k w e n t n i e jej u ż y w a j . • U s ł u g i t o j e d y n e m ie j s c e , w k t ó r y m m o ż n a d o d a w a ć p r o c e d u r y n a s ł u c h o w e d l a $ r o o t S c o p e . Jest t o z w i ą z a n e z t y m , że u s ł u g i n i e m a j ą w ł a s n e g o z a k r e s u . • U s ł u g i z ł o ż o n e z w i e l u w a r s t w s ą ś w ie t n e . Z a m i a s t t w o r z y ć j e d n ą g i g a n t y c z n ą u s ł u g ę r o b ią c ą w s z y s t k o , le p ie j je st p o d z i e l i ć ją n a c z ę ś c i, a p ó ź n ie j u t w o r z y ć j e d n ą w i ę k s z ą u s ł u g ę u ż y w a j ą c ą w s z y s t k i c h t y c h e l e m e n t ó w w z a le ż n o ś c i o d p o t r z e b y . • W y w o ł a n i a X H R w u s ł u g a c h p o w i n n o s ię w y k o n y w a ć z a p o m o c ą u s ł u g i $ h t t p . D z i ę k i t e m u w s z y s t k i e w y w o ł a n i a A P I b ę d ą w j e d n y m m ie j s c u , w k t ó r y m m o ż n a b ę d z ie z m i e n i a ć a d r e s y U R L . D l a s p ó j n o ś c i u s ł u g a p o w i n n a z w r a c a ć o b ie t n ic ę . • In t e g r a c j i p r o g r a m u z z e w n ę t r z n y m i b i b l i o t e k a m i u s ł u g o w y m i ( n i e d o t y c z ą c y m i in t e r f e j s u u ż y t k o w n i k a , j a k n p . S o c k e t I O ) p o w i n n o s ię d o k o n y w a ć p r z y u ż y c i u u s łu g . U m o ż l i w i a to in t e g ro w a n ie i z a m ie n ia n ie d o d a t k ó w w d o w o ln y m m o m e n c ie o ra z ic h im it o w a n ie w te sta c h je d n o stk o w y c h .

Najlepsze praktyki

| 269

Kontrolery Poniżej znajduje się kilka porad na tem at tw orzenia kontrolerów: • W pracy z kontroleram i lepiej jest używać nowszej składni lub definiować zm ienne i funkcje na obiekcie t h i s k ontrolera. To znaczy, że wszędzie, gdzie je st to możliwe, należy stosować składnię c o n t r o l l e r A s i unikać $ s c o p e . Nowa składnia jest bardziej zwięzła i zrozumiała. • Uważaj na zm ienną t h i s . N ajlepiej przypisać ją do lokalnej zm iennej (np. s e l f ) i posługiwać się tą zmienną. • Kontrolery nie powinny odnosić się do struktury D O M . Nie używaj biblioteki jQ uery bezpo­ średnio w kontrolerach. • K ontroler powinien zawierać tylko logikę prezentacyjną dotyczącą pobierania odpowiednich danych, ich prezentow ania na ekranie oraz obsługi in terak cji z użytkownikam i. I większość z tego powinna być przekazywana w razie m ożliwości do usługi. • Do obiektu t h i s kontrolera powinno się wstawiać tylko te zm ienne i funkcje, które są p o­ trzebne w kodzie H TM L. Jeśli coś nie jest potrzebne w kodzie H TM L, to powinno być zdefi­ niowane jako zm ienna lokalna kontrolera. W yjątkiem są oczywiście funkcje, które trzeba przetestować w testach jednostkowych. • Staraj się używać jednorazowego wiązania AngularJS (składnia : : wprowadzona w AngularJS 1.3 lub alternatywnie m ożna korzystać z otwartego rozwiązania b in d o n c e — h ttp s://g ith u b .co m / P a sv a z /b in d o n ce), które opisaliśm y w rozdziale 2. Dzięki tem u będziesz m ógł zaktualizować interfejs użytkownika raz, gdy pojawią się dane, i nie przejmować się więcej danym elementem. W pływ tego na wydajność aplikacji może być bardzo korzystny. • Jeżeli kontroler jest przeznaczony dla konkretnej trasy dostępnej przez adres U RL, to powi­ nien ładować wszystkie potrzebne dane wtedy, gdy jest tworzony jego egzemplarz. • Jeśli kontroler zapisuje stan całej aplikacji, to powinien to robić w usłudze, a nie w $ r o o t S c o p e . Nigdy nie używaj do tego celu $ r o o t S c o p e . • Kontrolery ($ b r o a d c a s t lub $ e m it ) mogą emitować zdarzenia we własnym zakresie albo wstrzy­ kiwać $ r o o t S c o p e i wywoływać zdarzenia na nim . Ale lepiej, żeby kontroler nie dodawał pro­ cedur nasłuchow ych dla $ r o o t S c o p e , ponieważ kontroler wraz zakresem może zostać znisz­ czony, a $ r o o t S c o p e jest dostępny w całej aplikacji wraz ze swoimi procedurami nasłuchowymi, które będą wyzwalane nawet wtedy, gdy kontroler będzie niedostępny.

Dyrektywy Dyrektywy należą do najbardziej przydatnych składników systemu AngularJS. Poniżej przedsta­ wiamy kilka porad, ja k je najlepiej wykorzystać: • Jeśli chcesz użyć zewnętrznego kom ponentu interfejsu użytkownika, dodaj go do aplikacji jako dyrektywę AngularJS. • Jeżeli potrzebujesz kom ponentów w ielokrotnego użytku, staraj się izolow ać zakres, aby z a ­ pobiec zm odyfikow aniu zakresu nadrzędnego i wyelim inow ać wszelkie zależności od jego składników .

270

|

Rozdział 15. Porady i najlepsze praktyki

• Jeśli reagujesz na zewnętrzne zdarzenia lub wywołania zwrotne i aktualizujesz model AngularJS, nie zapomnij wywołać funkcji $sco p e().$ a p p ly () — w przeciwnym razie interfejs użytkownika Twojej aplikacji nie będzie aktualizowany w odpowiednim czasie. • Jeżeli dodasz procedurę nasłuchu zdarzeń dla elementów spoza dyrektywy bądź użyjesz funkcji sondującej, pamiętaj, by je usunąć przy niszczeniu dyrektywy. • Jeśli tworzysz nowy zakres potom ny lub zakres izolowany, wykonuj operacje porządkowe na sygnał zdarzenia $scope $destroy. Ale jeśli dziedziczysz zakres nadrzędny, lepiej wykonać te operacje na sygnał zdarzenia $element $destroy. • Jeżeli kontroler musi udostępniać stan dyrektywie, to powinien: • Przekazać ten stan za pom ocą atrybutów H TM L (i izolowanego zakresu), jeśli kom ponent nie jest przypisany wyłącznie do jednej aplikacji i może być używany wielokrotnie. • Przekazywać tan stan za pom ocą usługi, jeśli kom ponent je st przypisany wyłącznie do jed n ej aplikacji. • Jeśli chcesz wykonywać pewną operację zawsze, gdy zm ieni się obiekt, przekaż go za pom ocą wiązania = na zakresie i dodaj $scope.$watch. • N igdy n ie p rz y p isu j p o n o w n ie referencji obiektu przekazanego przez zakres. Innym i słowy, jeśli obiekt s c o p e .fir s tO b je c t jest przekazywany przez wiązanie =, to w dyrektywie nie należy ustawiać ani nadpisywać wartości scope. f i rstObj ect. Modyfikacja klucza w obiekcie f i rstObj e ct jest dozwolona, ale ponowne przypisanie do zmiennej fir s tO b je c t nie powinno m ieć miejsca. • Do kontrolow ania sposobu i czasu aktualizowania modeli oraz ewentualnie w celu polepsze­ nia wydajności aplikacji używaj dyrektywy ngModelOptions. Dyrektywę tę m ożna zdefiniować dla elem entu najwyższego poziom u, aby była stosow ana wszędzie, zam iast deklarow ać ją wielokrotnie w różnych m iejscach.

Filtry Filtry są doskonałym narzędziem do ostatecznego form atow ania albo m odyfikow ania danych. O to dwie porady na tem at ich stosowania: • Każdy filtr użyty w kodzie H T M L jest wykonywany w każdym cyklu obliczeniow ym . Jeśli wiadomo, że dane nie będą się często zm ieniać, to bardziej efektywnym rozwiązaniem może być zastosowanie filtru bezpośrednio w kontrolerze, ja k pokazaliśm y w rozdziale 8. • Filtry powinny być bardzo szybkie, ponieważ mogą być wykonywane wielokrotnie w trakcie działania aplikacji. Dlatego nie powinno się w nich wykonywać żadnych intensywnych obli­ czeń ani modyfikować struktury D O M .

Narzędzia i biblioteki W ostatnim podrozdziale przedstawiamy kilka narzędzi i bibliotek ułatwiających pracę program i­ ście korzystającem u z systemu AngularJS. N iektóre z nich to narzędzia program istyczne, a inne to biblioteki kom ponentów i m oduły m ogące przydać się w projekcie.

Narzędzia i biblioteki

| 271

Batarang K a ż d y p r o g r a m i s t a p o s ł u g u j ą c y s ię s y s t e m e m A n g u l a r J S p o w i n i e n w y p o s a ż y ć s ię w r o z s z e r z e n ie p r z e g lą d a r k i C h r o m e o n a z w ie B a t a r a n g ( https://chrom e.google.com /w ebstore/detail/angularjs-batarang/

ighdm ehidhipcm cojjgtioacoafjm pfk?hl=en ) , k t ó r e u ł a t w i a d e b u g o w a n i e i o g ó l n i e p r a c ę . N a r z ę d z i e to d o d a j e k a r t ę A n g u l a r J S d o n a r z ę d z i d la p r o g r a m i s t ó w , j a k p o k a z a n o n a r y s u n k u 15.1.

Rysunek 15.1. Rozszerzenie Batarang w narzędziach dla programistów Chrome R o z s z e r z e n i e B a t a r a n g d o d a j e d o n a r z ę d z i d la p r o g r a m i s t ó w C h r o m e t r z y b a r d z o c ie k a w e i n i e ­ z m i e r n i e p r z y d a t n e k a rt y :

Models K a rta

M o d els ( m o d e l e ) z a w i e r a p o d g l ą d n a ż y w o h i e r a r c h i i z a k r e s ó w a p l i k a c j i A n g u l a r J S .

Z n a j d z ie s z n a n ie j w s z y s t k ie z a k r e s y a k t u a ln ie u ż y w a n e w w id o k u , a z a p o m o c ą m y s z y m o ż e s z p r z e g lą d a ć i c h z m i e n n e i fu n k c je . O b o k k a ż d e g o z a k r e s u z n a jd u je się i k o n a

< — m o ż n a ją k lik n ą ć ,

a b y p r z e j ś ć d o e le m e n t u H T M L , k t ó r e g o d o t y c z y d a n y z a k r e s , i d o w ie d z ie ć się, w k t ó r y m z a k r e ­ s ie t e n e le m e n t się z n a jd u je . P o d c z a s p r z e g lą d a n i a s t r o n y z a k r e s y s ą a k t u a liz o w a n e n a b ie ż ą c o .

Performance N a k a r c ie

P e r fo r m a n c e ( w y d a j n o ś ć ) z n a j d u j e s ię p o d g l ą d n a ż y w o w y d a j n o ś c i a p l i k a c j i

A n g u l a r J S . Jest t o p o s o r t o w a n a lis t a c z u j e k w y z w a l a n y c h w c y k l u o b l i c z e n i o w y m z w y k a z e m c z a s u w m i l i s e k u n d a c h i w u j ę c iu p r o c e n t o w y m i c h d z i a ł a n i a w c a ł y m c y k lu . I n f o r m a c j e te s ą p r z y d a t n e p r z y o p t y m a liz a c j i p r o g r a m u i s z u k a n iu w ą s k ic h g a rd e ł. W

r a z ie p o t r z e b y d a n e

m o ż n a w y e k s p o r t o w a ć d o f o r m a t u J S O N , a b y p ó ź n ie j p o r ó w n a ć je z n o w y m i d a n y m i w y g e ­ n e r o w a n y m i p o w p r o w a d z e n i u p o p r a w e k o p t y m a liz a c y j n y c h .

Dependencies N a k a r c ie

D ep en d en cies ( z a l e ż n o ś c i ) z n a j d u j e s ię w iz u a l i z a c j a s t r u k t u r y z a l e ż n o ś c i a p lik a c j i.

K a r t a t a z a w i e r a lis t ę w s z y s t k i c h u s ł u g d o s t a r c z a n y c h i t w o r z o n y c h o r a z t y c h , o d k t ó r y c h a p lik a c j a z a le ż y . D o d a t k o w o w y ś w i e t l a n a j e s t w i z u a l n a i n f o r m a c j a o z a l e ż n o ś c i a c h m i ę d z y u s ł u g a m i itd . Jest t o ś w i e t n a p o m o c d l a k o g o ś , k t o c h c e z b a d a ć s p o s ó b d z i a ł a n i a a p lik a c j i a lb o w y k r y ć n a j w a ż n ie j s z e i n a j c z ę ś c ie j u ż y w a n e u s łu g i . Z a p o m o c ą r o z s z e r z e n i a B a t a r a n g m o ż n a p r z e a n a l i z o w a ć d o w o l n ą d z ia ła j ą c ą a p lik a c j ę A n g u l a r J S . W y s t a r c z y t y lk o w łą c z y ć n a r z ę d z ia d la p r o g r a m is t ó w i u r u c h o m ić B a t a r a n g d la d a n e j w it r y n y in t e r n e t o w e j .

272

|

Rozdział 15. Porady i najlepsze praktyki

WebStorm Zintegrowane środowisko programistyczne (IDE) lub jego brak może m ieć ogromny wpływ na szyb­ kość powstawania aplikacji. A w przypadku języka JavaScript, który jest pozbawiony kompilatora, zabezpieczeń typów i m a dynamiczną naturę, solidne środowisko pracy jest podstawą. Aktualnie jednym z najlepszych IDE dla języka JavaScript jest W ebStorm (http://w w w .jetbrains.com /w ebstorm /) firmy Jetbrains. A co najważniejsze, środowisko to jest doskonale zintegrowane z systemami AngularJS i Karma. Pracę programiście niezwykle ułatwiają następujące funkcje: • automatyczne uzupełnianie atrybutów H TM L AngularJS, ja k n g -c lic k , n g -cla s s itd.; • możliwość przechodzenia do zewnętrznej (internetowej) dokumentacji każdej dyrektywy pod­ czas jej używania; • kliknięcie z w ciśniętym przyciskiem Ctrl (lub C o m m a n d ) dyrektywy, funkcji lub kontrolera w kodzie H TM L, powodujące przejście do definicji tej konstrukcji; • dodatki pom ocne przy refaktoryzacji, np. narzędzia do zm ieniania nazw zm iennych i w ła­ sności, a nawet dyrektyw; • szablony do tw orzenia szkieletów typowych konstrukcji, ja k kontrolery i dyrektywy; • integracja z systemem Karma, pozwalająca wykonywać testy jednostkowe bezpośrednio w śro­ dowisku W ebStorm . To zaledwie niewielka część wszystkich opcji. Tworzenie aplikacji AngularJS w środowisku W eb ­ Storm je st wysoce zalecane, warto więc pobrać 30-dniow ą wersję próbną tego narzędzia, aby je przynajm niej przetestować. Na pewno się nie zawiedziesz.

Moduły opcjonalne Na koniec przedstawiamy kilka modułów opcjonalnych udostępnianych przez rdzeń AngularJS, które również od czasu do czasu m ogą być przydatne: ngCookies Od lat ciasteczka to dane w form ie tekstowej. M oduł ngCookies zapewnia usługę umożliwia­ jącą pracę z ciasteczkami przeglądarki poprzez obiekt. Zamiast pracować z łańcucham i, można bezpośrednio zapisywać klucze tego obiektu i pobierać je jako obiekty. Moduł ten jest niezwykle przydatny w każdej aplikacji posługującej się ciasteczkami. n g San itize Jeżeli aplikacja wykorzystuje treść w ygenerow aną przez użytkowników i wyświetla ją w in ­ terfejsie użytkow nika, to m oże być podatna na ataki typu X S S , zw łaszcza gdy użytkow nik wpisze kod JavaScript i H TM L w m iejsce, w którym powinien wpisać zwykły tekst. M oduł n g San itize dostarcza A PI i dyrektywy do określania, które dane wejściowe m ają być spraw­ dzane i ja k należy je wyrenderować. Istnieje m ożliwość wyboru renderowania treści dokład­ nie w takiej postaci, w jakiej została wpisana, w form acie H TM L, a nawet w form acie H TM L z m ożliwością wykonania kodu JavaScript i CSS. M oduł ten jest niezbędny każdemu, kto pra­ cuje z treścią przekazywaną przez użytkowników.

Narzędzia i biblioteki

| 273

ngResource M oduł ngResource opisaliśmy w rozdziale 6., w podrozdziale „M oduł ngResource”. Jeśli uży­ wasz serwera udostępniającego końców ki R E ST , to m oduł ngResource m oże przyczynić się do znacznego zredukowania ilości kodu, jaką będziesz m usiał napisać. ngMessages Moduł ngMessages został wprowadzony w AngularJS 1.3 i opisaliśmy go w rozdziale 4., w punkcie „M oduł ngM essages”, w odniesieniu do m echanizm u obsługi błędów. M oduł ten udostępnia ulepszoną składnię i interfejs A PI do pracy z kom unikatam i o błędach w aplikacji. ngAria Podobnie jak ngMessages, moduł ngAria został wprowadzony w AngularJS 1.3. Jego celem jest podniesienie poziom u dostępności aplikacji przez zastosowanie atrybutów A RIA (http://w w w . w 3.org/T R /w ai-aria/), w których dostarcza się inform acji dla czytników ekranu i innych tech­ nologii wspomagających niepełnosprawnych użytkowników. Zazwyczaj wystarczające jest włą­ czenie modułu ngAria i wstrzyknięcie go do aplikacji, aby można było używać atrybutów ARIA w elem entach H TM L wykorzystujących dyrektywy ngModel, ngShow, ngC lick itd. W ięcej in ­ form acji m ożna znaleźć w oficjalnej dokum entacji systemu AngularJS (https://docs.an gu larjs. org/gu ide/accessibility ). ngTouch System AngularJS charakteryzuje się niewielkimi wymaganiami pamięciowymi i m ałą liczbą zależności, dzięki czemu doskonale sprawdza się także w urządzeniach mobilnych. Ten świetny dodatek wprowadza takie dyrektywy ja k n g -sw ip e left i ng-sw iperight do pracy z ekranam i dotykowymi. Ponadto obsługuje też tzw. f a s t c l ic k , czyli technologię um ożliwiającą natych­ miastowe reagowanie na zdarzenia dotknięcia przez urządzenia m obilne. ngAnimate W A ngularJS 1.2 wprowadzono op cjonalny m oduł ngAnimate. U m ożliw ia on definiow anie anim acji przejść w systemie AngularJS. Przy jego użyciu m ożna animować ukrywanie i poka­ zywanie elem entów obsługiw anych przez dyrektywy ng-show i ng-h ide, a nawet całe widoki i dodawanie oraz usuwanie klas. Gdy m oduł ngAnimate je st dodany do aplikacji, większość dyrektyw A ngularJS dostarcza punktów zaczepienia w postaci klas CSS służące do anim acji elem entów . Chcesz, aby interfejs użytkownika wjeżdżał na stronę spoza praw ej krawędzi ekranu? Chcesz, by elem ent w pętli zastępował inne elementy i pojawiał się powoli? W szystko to, i wiele więcej, możesz zrobić przy użyciu modułu ngAnimate.

Podsumowanie W tym rozdziale opisaliśmy wysokopoziomowe m etody testow ania i najlepsze sposoby optymal­ nego wykorzystania testów jednostkow ych i integracyjnych. Później udzieliliśm y odpowiedzi na jedno z najczęściej zadawanych pytań dotyczących systemu AngularJS: ja k powinna wyglądać struktura folderów projektu? Przedyskutowaliśmy kilka wartych zapamiętania koncepcji oraz p o­ kazaliśmy, ja k się one odnoszą do operacji CRU D .

274

|

Rozdział 15. Porady i najlepsze praktyki

P ó ź n ie j p r z e s z l i ś m y d o o m ó w i e n i a k w e s t i i z w i ą z a n y c h z j u ż g o t o w ą a p lik a c j ą , c z y li z jej b u d o w a ­ n i e m i w d r a ż a n i e m . O p i s a l i ś m y n a r z ę d z i e G r u n t i s p o s o b y j e g o in t e g r a c j i z p r o j e k t e m , a t a k ż e w s k a z a liś m y , n a c o n a le ż y z w r a c a ć u w a g ę p r z y w d r a ż a n iu p r o je k t ó w A n g u la r J S . K o le j n y m t e m a ­ t e m b y ł y n a j le p s z e p r a k t y k i p r o g r a m o w a n i a p r z y u ż y c i u A n g u l a r J S , z a r ó w n o d o t y c z ą c e z a g a d ­ n i e ń o g ó l n y c h , j a k i k o n k r e t n y c h , c z y l i k o n t r o l e r ó w , d y r e k t y w , u s ł u g i f ilt r ó w . N a z a k o ń c z e n i e d o k o n a liś m y p r z e g lą d u n a j c ie k a w s z y c h n a r z ę d z i i b ib lio t e k , t a k ic h j a k B a t a r a n g i W e b S t o r m , o ra z o p c jo n a ln y c h m o d u łó w d o s t a rc z a n y c h w ra z z s y st e m e m A n g u la rJ S . T o j u ż k o n i e c n a s z e j w s p ó l n e j p r z y g o d y . S t a r a l i ś m y s ię w m i a r ę s z c z e g ó ł o w o o p i s a ć w s z y s t k i e s k ł a d n i k i s y s t e m u , t a k a b y d a ło s ię c o ś z t e g o z r o z u m ie ć . O m ó w i e n i e w s z y s t k i e g o w k s ią ż c e o t a ­ k ie j o b j ę t o ś c i je st a b s o lu t n i e n i e m o ż l iw e , ale z d o b y t a w ie d z a s t a n o w i s o l i d n ą p o d s t a w ę , n a k t ó re j m o ż n a s z y b k o z b u d o w a ć in t e r e s u j ą c e i w y d a j n e a p lik a c j e s ie c io w e . U c z s ię n o w y c h r z e c z y i p r z y ­ łą c z s ię d o n a s z e j s p o ł e c z n o ś c i!

Podsumowanie

| 275

276

|

Rozdział 15. Porady i najlepsze praktyki

Skorowidz

A adres szablonu, 186 URL, 92, 124 AngularJS, 15 cykl życia, 211 dyrektywy, 179 filozofia systemu, 17 filtry, 135 formularze, 61 funkcje pomocnicze, 237 testowanie jednostkowe, 47 usługi, 61, 85 zalety systemu, 17 API usługi $http, 108 aplikacja powitalna, 25 aplikacje Ajax, 174 jednostronicowe, 153 typu CRUD, 262 atrybut replace, 199 when-select, 197

B biblioteka, 271 AngularJS, 164 jQuery, 10, 164 biblioteki zewnętrzne, 264 blok beforeEach, 57 budowanie projektu, 266

C CDN, content delivery network, 267 ciągła integracja, 260 controllerAs, 32 CRUD, create, read, update, delete, 18 cykl digest cycle, 75 obliczeniowy, 214 życia, 211, 213 życia dyrektywy, 215 czujki, 245

D debugowanie, 253 tras, 157 DOM, Document Object Model, 18 dostawca, 96 $httpProvider, 111 $routeProvider, 156 dyrektywa, 19, 27, 179, 270 form-element, 233, 235 ng-app, 164, 253 ng-bind, 35, 169 ng-checked, 81 ng-form, 77, 78 ng-include, 180, 182, 201 ng-messages, 71, 72 ng-model, 61, 63, 228 ng-model-options, 75 ng-repeat, 35, 39, 41, 42 ng-submit, 65 ng-switch, 183, 201 ng-transclude, 219, 220 ng-view, 162, 164 noUiSlider, 229

277

dyrektywa open-source, 37 pieChart, 240 stock-widget, 221 tabs, 225 ui-router, 176 widżetu giełdowego, 204 dyrektywy cykl życia, 215 klasowe, 189 kontrolery, 223 nazwa, 185 opcje podstawowe, 185 opcje zaawansowane, 211 procedura testowania, 203 sposób użycia, 188 testowanie, 203 wejściowe, 228 działanie filtra, 136, 140, 141 kontrolera, 31

E element , 164
, 71, 87
, 77

Nowa t ra s a < / h 1 > ' }); $ r o u t e P r o v id e r . w h e n ( '/ o l d ', r e d ir e c t T o : '/n e w '

{

});

W przykładzie tym system AngularJS otworzy adres /n ew , gdy użytkownik wpisze w przeglą­ darce adres /# /n ew lub /# /old.

158

I

Rozdział 10. Trasowanie przy użyciu modułu ngRoute

resolv e O statnią, a zarazem najbardziej w szechstronną i skom plikow aną opcją konfiguracyjną tras je st re so lv e . Szczegółowy opis m etod je j im plem entacji znajduje się w następnym punkcie. Ogólnie rzecz biorąc, opcja ta służy do wykonywania i kończenia asynchronicznych zadań przed załadowaniem określonej trasy. Pozwala to na sprawdzenie, czy użytkownik jest zalogowany i m a odpowiednie uprawnienia, albo na wczytanie pewnych danych przed załadowa­ niem do widoku kontrolera i trasy.

Wykonywanie zadań przed załadowaniem trasy za pomocą opcji resolve Jak napisaliśmy w poprzednim podrozdziale, w opcji reso lv e m ożna zdefiniować zadania asyn­ chroniczne do wykonania przed załadow aniem trasy. O pcja re so lv e to zestaw kluczy i funkcji. Każda funkcja może zwracać wartość lub obietnicę. Poniżej przedstawiono przykładową definicję reso lv e wykonującą wywołanie serwerowe i zwracającą wpisaną na sztywno wartość: a n g u la r . m o d u le ( 'r e s o l v e A p p [ ' n g R o u t e '] ) . v a l u e ( 'C o n s t a n t ',

{MAGIC_NUMBER: 42})

. c o n f i g ( [ '$ r o u t e P r o v i d e r ',

f u n c t io n ( $ r o u t e P r o v id e r )

{

$ r o u t e P r o v id e r . w h e n ( '/ ', { tem p late:

'< h 1 > S tr o n a głów na, bez r e s o lv e < / h 1 > '

} ) . w h e n ( '/ p r o t e c t e d ', { tem p late:

'< h 2 > S tr o n a pod o c h ro n ą < / h 2 > ',

r e s o lv e : { im m ediate:

[ 'C o n s t a n t ',

fu n c t io n ( C o n s t a n t )

{

r e t u r n Constant.M AGIC_NUMBER * 4; }], a sy n c :

[ '$ h t t p ',

f u n c t io n ( $ h t t p )

{

r e t u r n $ h t t p . g e t ( '/ a p i / h a s A c c e s s '); }] }, c o n t ro lle r:

[ '$ l o g ',

f u n c t io n ( $ lo g ,

'im m e d ia t e ',

im m ediate, asyn c)

'a s y n c ', {

$ l o g . l o g ( 'W a r t o ś ć im m ediate w ynosi

',

im m ediate);

$ l o g . l o g ( 'S e r w e r z w r ó c ił d la a s y n c ', a s y n c ); }] }); }]);

W przykładzie tym zakładamy, że pod adresem /a p i/h a sA ccess istnieje serwerowy interfejs API, który dla żądań GET zwraca odpowiedź o statusie 200, jeśli użytkownik m a dostęp, i innym , jeśli użytkownik nie m a dostępu. Zdefiniowane zostały dwie trasy. Pierwsza to standardowa trasa ładująca szablon H TM L, gdy z o ­ stanie napotkana je j definicja. Nie m a dla niej opcji reso lv e, więc zawsze ładuje się poprawnie. Druga definicja m a zdefiniowaną opcję resol ve z dwoma kluczami — immediate i async. Podkreślmy, że nazwy tych kluczy wybieramy sami i równie dobrze mogliśmy je nazwać mójKluczl i mój Kl ucz2. AngularJS nie wymusza stosowania żadnych konkretnych nazw. Każdy klucz jako w artość m a ta­ blicę będącą definicją wstrzykiwania zależności w stylu systemu AngularJS. Zależności opcji resolve definiujemy w tablicy i wstrzykujemy je do funkcji resolve. Pierwszy klucz, immediate, m a wstrzy­ kiwaną zależność Constant i zwraca je j w artość pom nożoną przez pewną liczbę. D rugi klucz,

Opcje trasowania

|

159

asyn c,

m a w strzykiw aną zależność $ h t t p i wysyła żądanie serwerow e na adres /a p i/h a s A c c e s s .

Później zwraca obietnicę dla tego konkretnego wywołania serwerowego. System AngularJS za­ pewnia następujące gwarancje: • Jeśli fu nkcja r e s o l v e zw róci wartość, system A ngularJS natychm iast kończy wykonywanie i traktuje to jako sygnał powodzenia. • Jeżeli funkcja r e s o l v e zwróci obietnicę, system AngularJS czeka na zwrot wartości przez tę obietnicę i uznaje powodzenie, gdy obietnica zostaje spełniona. Jeśli obietnica zostanie od­ rzucona, system uznaje to za niepowodzenie funkcji r e s o l v e . • Jeżeli zdefiniowane są funkcje r e s o l v e , system AngularJS wstrzymuje ładowanie tras do m o ­ m entu zakończenia wykonywania tych funkcji. Jeśli zawierają one wiele kluczy wykonujących wywołania asynchroniczne, AngularJS wykonuje je wszystkie równolegle i czeka na ich za­ kończenie, po czym dopiero ładuje stronę. • Jeżeli w którejkolw iek funkcji r e s o l v e wystąpi błąd lub którakolwiek ze zw róconych obietnic zostanie odrzucona (okaże się porażką), system AngularJS nie załaduje danej trasy. W poprzednim przykładzie klucz im m e d ia te w funkcji r e s o l v e zwraca tylko wartość, więc za każ­ dym razem jest uznawany za powodzenie. Klucz a s y n c wykonuje wywołanie serwerowe — od jego powodzenia zależy to, czy trasa zostanie załadowana. Jeśli serwer zwróci inny kod statusu niż 2 0 0 , system AngularJS nie wczyta strony. W przypadku problemów AngularJS ładuje i buforuje szablon, ale kontroler związany z trasą nie zostaje załadowany, przez co kod H TM L nie trafia do n g - v i e w . W takich przypadkach użytkownik nadal widzi stronę, którą przeglądał ostatnio. Nie jest to jednak najlepsze rozwiązanie pod względem użyteczności, ponieważ użytkownik nie zostanie poinfor­ m owany, że coś poszło źle. W dalszej części rozdziału przedstawiamy kom pletny przykład traso­ wania w systemie AngularJS. W arto też wiedzieć, że jeśli potrzebujem y danych, m ożem y pobrać w artość każdego z kluczy funkcji r e s o l v e wstrzykniętych do kontrolera. Każdy klucz może być wstrzyknięty do kontrolera bezpośrednio przez dodanie go jak o zależności. In fo rm acje te są dostępne obok innych p oten­ cjalnych zależności usługowych. Do kontrolera wstrzykiwane są następujące składniki: • sama wartość zwrócona przez funkcję r e s o l v e ; • rozwiązanie obietnicy, jeżeli funkcja r e s o l v e zwróciła obietnicę. Jeśli chodzi o klucz a s y n c , to otrzym ujem y rozwiązaną wartość obietnicy będącą obiektem odpo­ wiedzi od serwera z kluczami c o n f i g , s t a t u s , h e a d e r s i d a t a . O biekt ten jest norm alnie przekazy­ wany do funkcji s u c c e s s funkcji t h e n obietnicy: $ h t t p . g e t ( '/ a p i/ h a s A c c e s s ') . t h e n ( f u n c t i o n (re sp o n se )

{

c o n s o le . lo g ( 'P r z e k a z u j ą mnie do k o n t r o l e r a . ', r e s p o n s e ); r e t u r n re sp o n se ; });

W tym przypadku r e s p o n s e jest wartością klucza a s y n c podczas wstrzykiwania go do kontrolera.

160

|

Rozdział 10. Trasowanie przy użyciu modułu ngRoute

Usługa $routeParams K olejną rzeczą wartą odnotow ania i często wymaganą w jednostronicow ych aplikacjach jest kon­ tekst trasy. Na przykład czasami trzeba załadować określony wątek korespondencji albo wyświetlić szczegóły wybranej receptury. Inform acje te powinny znaleźć odzwierciedlenie w adresie URL, aby użytkownik m ógł zapisać go w ulubionych i za jakiś czas wrócić bezpośrednio w to samo m iej­ sce. W takich przypadkach zaleca się, by kontrolery pobierały identyfikatory i inform acje z adresu URL, zamiast polegać na stanie globalnym. Dane te są wykorzystywane do ładowania potrzebnych inform acji z serwera. W idealnej aplikacji jednostronicow ej kontroler i trasa powinny urucham iać się niezależnie, bez oczekiwania, że użytkownik najpierw wejdzie na stronę listy, a potem na stronę ze szczegółami. Param etry adresu U R L nie m uszą być wydobywane z samego adresu, lecz m ogą być pobierane z wygodnej usługi dostarczanej przez system AngularJS o nazwie $routeParams: a n g u la r . m o d u le ( 'r e s o l v e A p p ', [ ' n g R o u t e '] ) . c o n f i g ( [ '$ r o u t e P r o v i d e r ', f u n c t io n ( $ r o u t e P r o v id e r ) $ r o u t e P r o v id e r . w h e n ( '/ ', { tem p late: '< h 1 > S t r o n a głó w n a '

{

} ) . w h e n ( '/ d e t a i l / : d e t I d ', { tem p late: 'Z aład ow an o { { m y C t r l . d e t a i l I d } } , ' + ' łań cu c h z a p y ta n ia : { { m y C t r l . q S t r } } < / h 2 > ', c o n t r o ll e r : [ '$ r o u t e P a r a m s ', fu n c t io n ($ ro u t e P a ra m s ) t h i s . d e t a i l I d = $ ro u te P a ra m s.d e tId ; t h i s . q S t r = $ ro u te P a ra m s.q ; }], c o n t r o ll e r A s :

{

'm y C t r l'

}); }]);

W przykładzie tym trasa / nie je st ju ż dla nas niczym nowym. Gdy użytkow nik wejdzie pod ten adres, nastąpi załadowanie odpowiedniego szablonu. Druga trasa to / d e ta il/ :d e tId . Inform uje ona m echanizm trasowania AngularJS, że za częścią /d e ta il w adresie będzie się znajdować war­ tość, którą trzeba pobrać, zapisać i dostarczyć jak o d etId do kontrolera. Na przykład jeśli użyty zostanie adres /d e ta il/1 2 3 , to w zm ien n ej d etId zostanie zapisana w artość 123. Także adres /d e ta il/sh y a m pasuje do tej trasy i w tym przypadku zm ienna d etId będzie m iała wartość shyam. W yrażenie regularne w adresie w żaden sposób nie ogranicza typów parametrów. W kontrolerach dostęp do tych wartości m ożna uzyskać przez usługę $routeParams, która wczytuje adres U RL, przetwarza go oraz znajduje w nim wszystkie zm ienne, które następnie udostępnia nam do użytku w wygodnej postaci. Jeśli użytkownik wpisze adres typu /detail/123?q=M ySearchParam , AngularJS przetworzy go i usługa $routeParams udostępni następujące wartości: { d e t Id : '1 2 3 ', q: 'M ySe arch P a ram ' }

Nigdzie nie trzeba wywoływać funkcji parseURL ani niczego w tym rodzaju. Nasz kontroler ma bezpośredni dostęp do tych kluczy za pośrednictwem usługi $routeParams i może z nim i robić, co chcemy. Później m ożemy wykonać wywołanie serwerowe w celu załadowania danych albo prze­ tworzenia ich i odpowiednio ukrycia lub pokazania elementów interfejsu użytkownika.

Opcje trasowania

|

161

Na co trzeba uważać Zanim przedstawimy kom pletny przykład trasowania w systemie AngularJS z połączeniem z ser­ werem, opisujem y kilka kwestii, które nie są zbyt dobrze opisane w dokum entacji, ale ich n ie­ znajom ość może przysporzyć Ci wielu kłopotów podczas pracy nad aplikacją: P u ste szablony System AngularJS wymaga, aby każda trasa była powiązana z niepustym argum entem tem­ p la te lub tem plateU rl. O znacza to, że jeśli ich nie zdefiniujem y, system AngularJS porzuci daną trasę i nie będzie je j obsługiwał w interfejsie użytkownika. Jeżeli zdefiniujem y trasę wy­ konującą jakieś czynności przed przejściem na nową stronę (przykładem może być trasa wylogowywania), podczas których w interfejsie nic nie jest wyświetlane, to powinniśm y zdefi­ niować przynajm niej argum ent tem plate dla tej trasy. Pusty łańcuch również jest traktowany ja k brak szablonu! Rozw iązyw anie w strzykiw ania do k o n tro lera Jeżeli używamy funkcji reso lv e i chcem y wstrzyknąć wartości zależności do naszego kon tro­ lera, to kontroler ten powinniśm y zdefiniować w definicji trasy, a nie bezpośrednio w swoim kontrolerze zdefiniowanym za pom ocą dyrektywy n g -c o n tro lle r . W przeciwnym razie sys­ tem AngularJS nie będzie wiedział, który kontroler potrzebuje tych zależności, przez co nie będzie m ógł ich prawidłowo wstrzyknąć. Typ zm ien nej $routeParams Przy porównywaniu wartości otrzymywanych od usługi $routeParams z obiektam i z bazy da­ nych m ożna natknąć się na pewien problem . Na przykład jeśli w bazie danych przechowywa­ ne są identyfikatory liczbowe i chcem y je porów nać z danymi z usługi $routeParams, to po­ winniśm y m ieć się na baczności! Usługa ta domyślnie dla wszystkich kluczy zwraca łańcuchy. W efekcie wynik porów nania danych od $routeParams z liczbą z bazy danych za pom ocą ope­ ratora === zawsze będzie negatywny. N ajpierw obie wartości należy przekonwertować na ten sam typ, a dopiero potem je porównywać. Jed na dyrektywa ng-view n a ap likację Jest to ostatnia kwestia, o której nie m ożna zapom inać podczas pracy z m odułem ngRoute. W ap lik acji A n gu larJS w yk o rzy stu jącej m o d u ł ngRoute m oże b yć tylko je d n a dyrektyw a ng-view. Nie m ożna używać kilku tych dyrektyw ani ich zagnieżdżać. Jest to spowodowane głównie tym, że dyrektywa ng-view działa w bardzo prosty sposób — wykrywa zmianę adresu U R L i w reakcji na nią zm ienia treść zgodnie z definicją trasy. Gdyby było kilka tych dyrek­ tyw, to ta sam a treść zostałaby wyświetlona wiele razy. Gdybyśmy zagnieździli dyrektywy ngviews, to jed na treść zostałaby wyświetlona wewnątrz innej, co nie byłoby dla nas dobrym rozwiązaniem.

Kompletny przykład trasowania W tym punkcie przedstawiamy kom pletny przykład trasowania w aplikacji AngularJS i wykorzy­ stania usługi $h ttp do k om unikacji z serwerem . Będzie to aplikacja o nazwie FIF A T eam s, wy­ świetlająca listę niektórych drużyn piłkarskich.

162

|

Rozdział 10. Trasowanie przy użyciu modułu ngRoute

Zanim przejdziem y do kodu, zobaczmy, ja k ogólnie m a działać nasza aplikacja: • Strona główna przedstawia listę drużyn piłkarskich. Na stronę tę może wejść każdy. • Strona logowania dla użytkowników. Na stronę tę może wejść każdy. • Strony ze szczegółowymi inform acjam i o drużynach nie są dostępne dla wszystkich, a jedynie dla zalogow anych użytkowników. D ostęp je st m ożliwy zarówno dla tych, którzy zalogowali się krótko przed w ejściem na stro n ę, ja k i dla tych, którzy zalogow ali się i zam knęli okno, a potem wrócili na stronę. Jest to możliwe dzięki temu, że sesja zalogowania jest obsługiwana na serwerze, a nie u klienta. Aby nie komplikować nadmiernie aplikacji, już wcześniej utworzyliśmy serwer w NodeJS. Dzięki temu m ożem y skupić się wyłącznie na trasowaniu i kom unikacji z serwerem. Kod źródłowy m ożna po­ brać z serwera FT P znajdującego się pod adresem ftp ://ftp .h elio n .p l/p rz y k la d y /an g u sw .z ip . W folderze r10/rou tin g-exam ple wykonaj poniższe polecenie, by zainstalować potrzebne zależności. npm i n s t a l l

Następnie wykonaj poniższe polecenie, żeby uruchom ić serwer: node s e r v e r . j s

Później możesz wejść na stronę h ttp ://localh ost:8 0 0 0 , aby zobaczyć aplikację w akcji. Poniżej znajduje się kod źródłowy pliku index.htm l omawianej aplikacji: W y lo g u j s ię < / a > < /d i v> < / d iv > < d iv n g -v ie w > < / d iv > < s c r i pt s r c = " s c r i p t s / v e n d o r s / j q u e r y - 1 . 1 1 . 1 . j s " > < / s c r ip t > < s c r i p t s r c = " s c r ip t s / v e n d o r s / a n g u la r . j s " > < / s c r ip t > < s c r i p t s r c = " s c r i p t s / v e n d o r s / a n g u la r - r o u t e . j s " > < / s c r ip t >

Opcje trasowania

|

163

< s c r ip t s r c = "s c r ip t s / a p p . j s "> < / s c r ip t > < s c r i p t s r c = " s c r i p t s / s e r v i c e s . j s " > < / s c r i pt> < s c r ip t s r c = "s c r ip t s / c o n t r o lle r s . j s "> < / s c r ip t >


W pliku tym znajduje się kilka interesujących elementów: • Dyrektywa ng-app, określająca, gdzie należy szukać kontrolerów , konfigu racji itp., została zdefiniow ana dla elem entu . • Utworzono górny pasek z osobnym kontrolerem . W pasku tym będą wyświetlane logo oraz odnośniki logowania i wylogowywania. Do sterow ania tym, który odnośnik m a być wyświe­ tlony, i obsługi stanu logowania na poziomie aplikacji utworzyliśmy usługę U serService (zaraz się nią zajm iem y). O dnośniki są wyświetlane i ukrywane w zależności od tej usługi. • Dyrektywa ng-view została zdefiniowana w części dokumentu m ającej reagować na zmiany adresu URL. • Ładujemy biblioteki jQ uery i AngularJS i dopiero po ich załadowaniu dołączamy kod źródłowy swojej aplikacji. N ajbardziej godne uwagi w tym kodzie są odnośniki logow ania i wylogowywania, które są wy­ świetlane i ukrywane za pom ocą dyrektyw ng-show i ng-hide. Steruje nim i usługa U serService, a nie kontroler, więc bez względu na to, jak a strona je st wyświetlona, odnośniki te zawsze będą odpowiednio przedstawione. Z anim zajrzymy do pliku app.js, w którym zdefiniowaliśmy trasy i konfigurację, przyjrzymy się usługom zdefiniowanym w pliku services.js: // Plik: r10lrouting-examplelapplscriptslservices.js a n g u la r . m o d u le ( 'f i fa A p p ') . f a c t o r y ( ' Fi f a S e r v i c e ', f u n c t io n ( $ h t t p ) {

[ '$ h t t p ',

retu rn { getTeam s: f u n c t io n ( ) { r e t u r n $ h t t p . g e t ( '/ a p i/ t e a m ') ; }, g e tT e a m D e ta ils: f u n c t io n (c o d e ) { r e t u r n $ h t t p . g e t ( '/ a p i/ t e a m / ' + cod e); } } }]) . f a c t o r y ( 'U s e r S e r v i c e ', v a r s e r v ic e = { is L o g g e d In : f a l s e ,

[ '$ h t t p ',

f u n c t io n ( $ h t t p )

s e s s io n : f u n c t io n ( ) { r e t u r n $ h t t p . g e t ( '/ a p i / s e s s i o n ') . t h e n ( f u n c t io n ( r e s p o n s e ) { s e r v ic e . is L o g g e d In = tru e ; r e t u r n re sp o n se ; }); },

164

|

Rozdział 10. Trasowanie przy użyciu modułu ngRoute

{

lo g i n : f u n c t io n ( u s e r ) { r e t u r n $ h t t p . p o s t ( '/ a p i / l o g i n ', . t h e n ( f u n c t io n ( r e s p o n s e ) { s e r v ic e . is L o g g e d In = tru e ; r e t u r n re sp o n se ;

u se r)

}); } }; r e t u r n s e r v ic e ; }]);

Zdefiniowaliśm y dwie usługi: F ifa S e r v ic e i U serService. Usługa F ifa S e r v ic e służy do pobierania listy drużyn i in form acji o n ich z serwera za pom ocą żądania H T T P GET. Usługa U serService również zawiera dwie m etody — jed ną do sprawdzania, czy bieżący użytkownik m a aktywną sesję na serwerze, a drugą do logowania użytkownika. Serwer zapewnia następujące punkty dostępowe: • Żądanie GET na adres /api/team zwraca listę drużyn zarejestrow anych w system ie w postaci tablicy. • Żądanie GET na adres /api/team/:code, gdzie code je st kodem drużyny, zwraca inform acje o wybranej drużynie w postaci obiektu. • Żądanie GET na adres /api/session zwraca kod statusu 400, jeśli użytkownik jest niezalogowany, lub obiekt z danymi użytkownika, jeśli jest on zalogowany. To umożliwia klientowi (aplikacji sieciow ej) sprawdzenie, czy użytkownik jest zalogowany na serwerze. • Żądanie POST na adres /api/login z danymi w postaci {username:

'u żytkow nik', password:

'h a s ł o '} pow oduje próbę zalogow ania użytkow nika. Jeżeli próba się pow iedzie, zostanie zw rócone to, co zw róci wyw ołanie sesji. W przeciw nym wypadku, je śli nie uda się uw ie­ rzytelnić użytkow nika, zw rócony zostanie błąd 400 z polem msg z w yjaśnieniem powodu niepow odzenia. W szystkie usługowe interfejsy A PI wysyłające żądania H T T P zw racają obietnice um ożliw iające kontrolerom i innym usługom łączenie i wykonywanie kolejn ych zadań. Zawartość pliku controllers.js wygląda następująco: / / Plik: r10/routing-example/app/scripts/controllers.js a n g u la r . m o d u le ( 'f i f a A p p ') . c o n t r o l l e r ( 'M a i n C t r l ', f u n c t io n ( U s e r S e r v ic e ) v a r s e lf = t h is ;

[ 'U s e r S e r v i c e ', {

s e l f . u s e r S e r v i c e = U s e rS e rv ic e ;

/ / sprawdzenie, czy użytkownik jest zalogowany podczas ładow ania aplikacji / / Usługa UserService autom atycznie zaktualizuje zm ienną isLoggedIn p o zakończeniu tego wywołania. U s e r S e r v ic e . s e s s io n () ; }]) . c o n t r o lle r ( 'T e a m L i s t C t r l ', f u n c t io n ( F i f a S e r v i c e ) { v a r s e lf = t h is ;

[ 'F i f a S e r v i c e ' ,

se l f.te a m s = [ ]; Fi f a S e r v ic e . g e t T e a m s ( ) . t h e n ( f u n c t io n ( r e s p ) s e lf . t e a m s = r e s p . d a t a ;

{

Opcje trasowania

|

165

}); }]) . c o n t r o l l e r ( 'L o g i n C t r l ', [ 'U s e r S e r v i c e ', f u n c t io n ( U s e r S e r v ic e , $ lo c a t io n ) { var s e lf = t h is ; s e l f . u s e r = {usernam e: ' ' , p assw ord:

'$ l o c a t i o n ',

''} ;

s e l f . l o g i n = f u n c t io n ( ) { U s e r S e r v i c e . l o g i n ( s e l f . u s e r ) . t h e n ( f u n c t io n ( s u c c e s s ) $ l o c a t i o n . p a t h ( '/ t e a m '); }, f u n c t io n ( e r r o r ) { s e lf . e r r o r M e s s a g e = e rr o r. d a t a .m s g ;

{

}) }; }]) . c o n t r o l l e r ( 'T e a m D e t a i l s C t r l ', [ '$ l o c a t i o n ', '$ r o u t e P a r a m s ', 'F i f a S e r v i c e ', f u n c t io n ( $ lo c a t io n , $ rou te Pa ra m s, F if a S e r v ic e ) { var s e lf = t h is ; s e lf. te a m = { } ; Fi fa S e rv ic e .g e t T e a m D e t a ils ($ ro u t e P a r a m s .c o d e ) . t h e n ( f u n c t io n ( r e s p ) { s e lf. t e a m = r e s p . d a t a ; }, f u n c t i o n ( e r r o r ) { $ l o c a t i o n . p a t h ( '/ l o g i n ') ; }); }]);

W aplikacji tej zdefiniowaliśmy cztery kontrolery: • MainCtrl obsługuje górny pasek nawigacyjny i dostarcza usługę U serService do widoku, dzięki czemu m ożna pokazywać i ukrywać odnośniki dotyczące logowania w zależności od stanu użytkownika. Ponadto kontroler ten wykonuje wywołanie serwerowe w celu sprawdzenia, czy użytkownik jest zalogowany podczas ładowania aplikacji. • TeamListCtrl obsługuje trasę strony głównej i za pom ocą usługi F ifa S e rv ic e pobiera listę drużyn podczas ładowania. Później dostarcza te dane do widoku w celu ich wyświetlenia. • LoginCtrl zawiera tylko jed ną funkcję, pozwalającą użytkownikowi zalogować się po wpisa­ niu swojej nazwy i hasła oraz naciśnięciu przycisku logowania. Jeśli operacja powiedzie się, nastąpi przekierowanie na stronę główną. W przypadku błędu w interfejsie użytkownika zo­ stanie wyświetlona inform acja o błędzie. • Team D etailsC trl to jedyny kontroler, który robi coś niezwykłego i ciekawego, a konkretnie wczytuje konkretn ą drużynę w zależności od trasy. Przypuśćm y, że użytkow nik wejdzie na stronę h ttp://localh ost:8 0 0 0 /# /team /E S P . Spowoduje to uruchom ienie trasy ładującej kontro­ ler Team DetailsCtrl — w tym m om encie musi on wykryć, dla której drużyny został załado­ wany. Inform acje te może pobrać z usługi o nazwie $routeParams. Usługa ta zawiera w kluczu code kod bieżącej drużyny. Klucz ten jest ustawiany przez trasow anie, do czego za chwilę dojdziem y. Później kontroler wczytuje inform acje o drużynie z serwera na podstawie tego kodu pobranego z adresu URL.

166

|

Rozdział 10. Trasowanie przy użyciu modułu ngRoute

Wszystkie żądania serwerowe są obsługiwane przez usługę $ h t t p i zwracają obietnicę, więc w każ­ dym m iejscu, w którym chcem y być powiadomieni o zakończeniu operacji lub potrzebujem y wyniku (np. załadowanie drużyn bądź pojedynczej drużyny albo wywołanie dotyczące logowania), dodajem y wywołanie funkcji . t h e n ( ) . Przyda się też rzut oka na fragmenty kodu HTM L. Są to tylko części dokumentów, a nie kompletne strony, więc nie m a w nich elementów < h t m l> , < h e a d > ani < b o d y > . Zaczniem y od zawartości pliku team _list.htm l: < d iv c l a s s = " t e a m - li s t - c o n t a i n e r " > < d iv c la s s = "t e a m " n g -re p e a t= "te a m in t e a m L is t C t r l. te a m s < d iv c la s s = " t e a m - in f o row "> < d iv c l a s s = " c o l - l g - 1 r a n k ">

| o rd e rB y:

'r a n k '" >

< / s p a n > < d iv c l a s s = " c o l- s m - 3 " > < d iv c l a s s = " c o l - l g - 6 name">
< / a > < / d iv > < / d iv > < / d iv >

Dane do wyświetlenia na liście drużyn są pobierane z kontrolera za pom ocą dyrektywy n g - r e p e a t . Każda znajdująca się na niej nazwa jest odnośnikiem z dyrektywą n g - h r e f do strony ze szczegó­ łowymi inform acjam i. Do wyświetlania obrazów użyto dyrektywy n g - s r c . Dyrektywa ng-href jest stosowana z dynamicznymi adresami URL. Choć można też korzystać z instrukcji typu h re f= "{{ctrl.m y U rl}}", zalecan e jest używanie zamiast nich dyrektywy ng-href. Zalecenie to bierze się z tego, że podczas pierwszego ładowania aplikacji, zanim włączy się system AngularJS i podmieni wartości, atrybut href przez krótki czas ma wartość {{c trl.m y U rl}}. Rozwiązaniem tego problemu jest właśnie używanie dy­ rektywy ng-href, która początkowo pozostawia atrybut href bez treści i wstawia do niego wartość ctrl.myUrl dopiero wtedy, gdy system AngularJS zacznie działać. Analogicznie dyrektywa ng-src służy do prezentowania obrazów o dynamicznych adresach URL. Gdybyśmy w kodzie HTML napisali src=" {{ctrl.m yIm g}}", to z do­ kumentu zostałoby wysłane żądanie pobrania z serwera obrazu {{ctrl.m yIm g}}, co oczywiście spowodowałoby błąd. Dlatego lepiej jest używać dyrektywy ng-src, która pozwala włączyć się systemowi AngularJS i ustawić atrybut src elementu od razu na odpowiednią wartość. W ten sposób zostaje wyeliminowane jedno błędne żądanie do serwera.

Opcje trasowania

|

167

N astępna jest strona login.htm l: < d iv c l a s s = " l o g i n - c o n t a i n e r" n g - c o n t r o ll e r = " L o g i n C t r l as l o g i n C t r l " > < d iv c l a s s = " a l e r t a le r t - d a n g e r " n g - b i n d = " lo g in C t r l. e r r o r M e s s a g e " n g - s h o w = " lo g in C t r l. e r r o r M e s s a g e "> < / d i v> < d iv c l a s s = " c a r d lo g i n - c a r d " > < d iv c l a s s = " l o g in - f o r m " >
< d iv c la s s = " f o r m - g r o u p "> < la b e l fo r= "e m a il"> N a z w a u ż y tk o w n ik a < / la b e l> < in p u t t y p e = "t e x t " n g - m o d e l= "lo g in C t r l. u s e r . u s e r n a m e " c l a s s = " f o r m - c o n t r o l" id = "e m a il" p la c e h o ld e r= "W p is z nazwę u ż y tk o w n ik a " re q u i r e d = ""> < d iv c la s s = " f o r m - g r o u p "> < la b e l f o r = "p a s s w o r d "> H a s io < / la b e l> < in p u t ty p e = "p a ssw o rd " n g -m o d e l= "lo g i n C t r l. u s e r . p a s s w o r d " c l a s s = " f o r m - c o n t r o l" id = "p a ss w o r d " p la c e h o ld e r= "W p is z h a s ło " re q u i r e d = ""> < in p u t t y p e = "s u b m it " c la s s= "b tn b tn -su c ce ss b t n - lg " v a lu e = "Z a lo g u j s i ę " n g - d i s a b le d = "lo g in F o r m . $ i n v a li d " >
< / d iv >

Plik login .htm l zawiera prosty form ularz z polam i na nazwę użytkownika i hasło. D efiniuje też bezpośrednio kontroler za pom ocą dyrektywy n g -c o n tro lle r i wykorzystuje dyrektywę ng-submit do wywoływania funkcji kontrolera obsługującej logowanie. Znajduje się w nim także sekcja słu­ żąca do wyświetlenia ewentualnej wiadomości o błędzie. O statnia strona to te a m _ d eta ils .h tm l, na której wyświetlane są wszystkie in form acje o drużynie w eleganckiej form ie: < d iv c l a s s = " s t o c k - d a s h " > Nazwa: Cena:
| c u r re n c y ">

n g - b in d = "m a in C t r l. g e t C h a n g e (s t o c k ) + '% '" >
< / d iv >

Jest to bardzo prosty kod H TM L, w którym pobieram y zm ien ną o nazwie s to ck i wyświetlamy znajdujące się w niej nazwę i cenę w osobnych elem entach . W ostatnim z tych elementów wyw ołujem y fu nkcję kontrolera m ainC trl, obliczającą i wyśw ietlającą procentow ą zm ianę ceny akcji. Teraz spójrz na zawartość pliku in d ex .h tm l tej aplikacji:

180

|

Rozdział 11. Dyrektywy

< h tm l> < t i t l e > A p l i k a c j a g ie id o w a < / t it le > < d iv n g - c o n t r o ll e r = " M a in C t r l as m a in C t r l" > < h 3 > L is t a a k c ji< / h 3 > < d iv n g - r e p e a t = "s t o c k in m a in C t r l. s t o c k s " > < d iv n g - in c lu d e = "m a in C t r l. s t o c k T e m p la t e "> < / d iv > < /d i v> < / d iv > < s c r i pt s r c = " h t t p s :/ / a j a x . g o o g le a p is . c o m / a ja x / li b s / a n g u la r j s / 1 . 3 . 1 1 / a n g u l a r . j s " > < / s c r i pt> < s c r i p t s r c = " a p p . j s " > < / s c r i pt>

W głównym pliku in dex .h tm l ładujem y bibliotekę AngularJS i kod naszej aplikacji oraz inicjujem y ng-app (stockMarketApp). Następnie ładujem y kontroler w głównym elemencie
, w którym wyświetlamy listę akcji. Treść dla dyrektywy ng-repeat wstawiliśmy do pliku stock.h tm l, zamiast wpisywać ją bezpośrednio w tym samym dokumencie. Następnie za pom ocą dyrektywy ng-include ładujem y to, co wskazuje m ainC trl.stockTem plate. Teraz zobaczmy, ja k działa kontroler: / / Plik: rozdziai11/ng-include/app.js a n g u la r . m o d u le ( 's t o c k M a r k e t A p p ', [ ]) . c o n t r o l l e r ( 'M a i n C t r l ', [ f u n c t io n ( ) var s e lf = t h is ;

{

s e lf. s t o c k s = [ {name: 'P ie r w s z y t o w a r ', p r ic e : 100, p r e v io u s : 22 0 }, {name: 'D r u g i t o w a r ', p r ic e : 140, p r e v io u s : 120}, {name: 'T r z e c i t o w a r ', p r ic e : 110, p r e v io u s : 110 }, {name: 'C z w a rty t o w a r ', p r ic e : 400, p r e v io u s : 420} ]; s e lf . s t o c k T e m p la t e = 's t o c k . h t m l '; s e lf. g e t C h a n g e = f u n c t io n ( s t o c k )

{

r e t u r n M a t h . c e i l ( ( ( s t o c k . p r i c e - s t o c k . p r e v io u s ) / s t o c k . p r e v io u s ) * 100 ); }; }]);

Kontroler MainCtrl zawiera definicję listy towarów, z których każdy m a określoną nazwę, bieżącą cenę i poprzednią cenę. Ponadto zdefiniowaliśmy w nim funkcję getChange, służącą do obliczania procentowej zmiany cen przekazanego do niej towaru (dodatkowo zaokrąglamy otrzymaną wartość, aby lepiej wyglądała).

Alternatywa dla dyrektyw własnych

|

181

W pliku index.htm l zamieściliśmy następujący element: < d iv n g - in c l u d e = "m a in C trl.s to c k T e m p la t e "> < / d i v>

Zmusiło nas to do zdefiniowania zmiennej w kontrolerze i do odwołania się do niej. Inną możliwością jest wpisanie łańcucha bezpośrednio do kodu HTML w dyrektywie ng-include. W takim przypadku musimy zaznaczyć, że nie przekazaliśmy zmiennej z kontrolera, tylko samą wartość. Można to zrobić w następujący sposób: < d iv n g - in c l u d e = " 'v ie w s / s t o c k . h t m l'" > < / d iv >

Zwróć uwagę na pojedynczy cudzysłów w podwójnym cudzysłowie. Pojedynczy cu­ dzysłów stanowi dla systemu AngularJS sygnał, że przekazana wartość jest literałem łańcuchowym, a nie zmienną. Gdyby nie ten pojedynczy cudzysłów, AngularJS szu­ kałby zmiennej o nazwie views/stock.html (której oczywiście by nie znalazł, bo zmienna nie może mieć takiej nazwy) i zgłosiłby błąd z informacją o tym, że niedozwolona nazwa zmiennej uniemożliwia prawidłowe przetworzenie wyrażenia. Jeśli więc bez­ pośrednio wpisujesz ścieżkę do pliku, nie zapomnij dodać pojedynczego cudzysłowu. Do serwowania fragm entów dokumentu H TM L zawsze potrzebny jest serwer H TT P , ponieważ przeglądarki nie pozwalają na dołączanie plików za pośrednictw em protokołu file ://. Aby więc um ożliw ić działanie przedstaw ionej aplikacji na m aszynie lokaln ej, m am y kilka m ożliw ości do wyboru: • Zainstalować serwer Node h ttp -serv er za pomocą polecenia sudo npm in s ta ll -g h ttp -serv er (w systemie W indows należy pom inąć elem ent sudo). Później należy uruchom ić h ttp -s e rv e r w katalogu zawierającym plik in d ex .h tm l. • Fani Pythona m ogą wykonać polecenie python -m SimpleHTTPServer w folderze, w którym znajduje się plik in dex.htm l. • W ebStorm może uruchom ić wbudowany serwer, gdy poprosim y o otwarcie pliku in d ex .h tm l w przeglądarce. Jeśli teraz uruchom isz aplikację, zobaczysz stronę H TM L z listą czterech towarów, z których każ­ dy m a określoną nazwę, cenę i procentow ą zmianę ceny. Dyrektywę ng-include wykorzystaliśmy do przeniesienia do osobnego m niejszego pliku treści, którą inaczej musielibyśmy wpisać w pliku in dex.htm l. Jest to największa zaleta dyrektywy n g -in cl ude. Jeżeli pliki H TM L są duże, m ożna łatwo je podzielić na m niejsze i łatwiejsze do opanowania części, które będą pełniły podobną funkcję co moduły.

Ograniczenia dyrektywy ng-include C h oć dyrektywa ng-includ e świetnie nadaje się do dołączania m ałych plików z kodem H TM L do aplikacji, m a ona pewne wady. Utw orzony przez nas plik sto ck .h tm l, jeśli dołączymy go do p ro­ gramu za pom ocą dyrektywy ng-includ e, będzie m iał dwa poważne ograniczenia: • Aktualnie plik stock.h tm l szuka zmiennej o nazwie stock i wyświetla zawarte w niej inform acje dotyczące nazwy, ceny i procentowej zmiany ceny towaru. Gdybyśmy teraz w pliku in dex.htm l zm ienili pętlę sto ck in m a in C trl.sto ck s na each in m a in C trl.sto ck s, to na stronie zostały­ by wyświetlone cztery puste bloki, bez nazwy, ceny i zm ienionej ceny. Powodem tego jest to,

182

|

Rozdział 11. Dyrektywy

że m im o iż zm ienilibyśmy nazwę zm iennej w pliku in d ex .h tm l, plik sto ck.h tm l nadal oczeki­ wałby zm iennej o nazwie sto ck do wyświetlenia. Zatem jeśli używasz dyrektywy ng-include, w każdym pliku do którego dołączasz plik stock.h tm l, musisz zastosować nazwę zm iennej za­ w ierającej inform acje o towarach. • Aktualnie plik sto ck .h tm l m usi być używany z kontrolerem m ainCtrl, ponieważ wywołujemy w nim funkcję m ainCtrl.getChange, służącą do obliczania procentow ej zm iany ceny towaru. Jeżeli więc użyjemy tego pliku w innej aplikacji, w której nie ma kontrolera o nazwie mainCtrl lub kontroler ten nie zawiera funkcji getChange(), to nie uda się wyświetlić części inform acji. Zatem zachowanie dołączanego pliku z kodem H T M L jest zależne od ręcznie wprowadza­ nych ustawień. W punkcie „Tworzenie dyrektywy” pokazujem y rozwiązania obu tych problemów.

Dyrektywa ng-switch Inną dyrektywą, za pom ocą której m ożna dodać do interfejsu użytkownika elem enty funkcjonal­ ności pozwalające na selektywne wyświetlanie fragmentów kodu H TM L, jest ng-switch. Umożliwia ona warunkowe dołączanie fragm entów kodu H TM L w podobny sposób ja k znane z różnych ję ­ zyków program owania konstrukcje switch. Poniżej znajduje się prosty przykład użycia dyrektywy ng-sw itch: < h tm l> < t i t l e > A p l i k a c j a z p r z e T ą c z n ik ie m < / t it le > < d iv n g - c o n t r o ll e r = " M a in C t r l as m a in C t r l" >

Warunkowe elem enty w HTML

< b u tto n n g - c lic k = "m a in C t r l. c u r r e n t T a b = 't a b 1 '" > K a rta 1.
< b u tto n n g - c lic k = "m a in C t r l. c u r r e n t T a b = 't a b 2 '" > K a rta 2. < b u tto n n g - c lic k = "m a in C t r l. c u r r e n t T a b = 't a b 3 '" > K a rta 3. < b u tto n n g - c lic k = "m a in C t r l. c u r r e n t T a b = 's o m e t h in g '" > Dom yślna k a rt a < d iv n g - s w it c h = "m a in C t r l. c u r r e n t T a b "> < d iv n g -s w it c h -w h e n = "t a b 1 "> Wybrano k a rtę 1. < / d iv > < d iv n g -s w it c h -w h e n = "t a b 2 "> Wybrano k a rtę 2. < / d iv >

Alternatywa dla dyrektyw własnych

|

183

< d iv n g -s w it c h -w h e n = "t a b 3 "> Wybrano k a rtę 3. < / d iv > < d iv n g - s w it c h - d e f a u lt > N ie wybrano żadnej znanej k a rt y < / d iv > < /d i v> < s c r i pt s r c = " h t t p s :/ / a j a x . g o o g le a p i s . co m /a jax /li b s / a n g u la r j s / 1 . 3 . 1 1 / a n g u l a r . j s " > < / s c r i pt> < s c r i p t t y p e = "t e x t / j a v a s c r i p t "> a n g u la r . m o d u le ( 's w it c h A p p ', []) . c o n t r o l l e r ( 'M a i n C t r l ', [ f u n c t io n ( ) t h is . c u r r e n t T a b = 't a b 1 '; }]); < / s c r ip t >

{

Je st t o p r o s t a a p lik a c j a w y ś w i e t la j ą c a p ię ć p r z y c i s k ó w . K l i k n i ę c i e j e d n e g o z t r z e c h p i e r w s z y c h p o w o d u j e o t w a r c ie o d p o w i e d n i e j k a r t y . D w a o s t a t n ie p r z y c i s k i u s t a w ia j ą l o s o w ą w a r t o ś ć d la b i e ­ ż ą c e j k a r t y . W ó w c z a s n a s t ę p u j e u r u c h o m i e n i e d o m y ś l n e g o p r z y p a d k u i w y ś w ie t le n ie o s t a t n ie g o e le m e n t u < d iv > . A b y u r u c h o m i ć tę a p lik a c j ę , n a l e ż y w y k o n a ć te s a m e c z y n n o ś c i c o p o p r z e d n i o , c z y li u r u c h o m i ć lo k a ln y se rw e r. D z i a ł a n i e t e g o p r o g r a m u o p i e r a s ię n a d y r e k t y w i e n g - s w i t c h i w a r t o ś c i z m i e n n e j c u r r e n t T a b . W

g ł ó w n y m e le m e n c ie < d i v > z d e f i n i o w a l i ś m y p o t o m n e s e k c j e < d i v > ; w y ś w i e t l a m y je s e le k t y w n ie

w z a l e ż n o ś c i o d t e g o , k t ó r y p r z y c i s k z o s t a n i e k l i k n i ę t y . F u n k c j o n a l n o ś ć tę z r e a l i z o w a l i ś m y z a p o m o c ą a t r y b u t u n g - s w it c h - w h e n

w

e le m e n t a c h p o t o m n y c h . D o d a j e m y w a r u n k i (ja k i n ­

s t r u k c j a s e l e c t ) . K a ż d y a t r y b u t n g - s w it c h - w h e n p o b ie r a ł a ń c u c h (n p . " h a l o " ) . Je śli ł a ń c u c h t e n p a ­ s u je d o w a r t o ś c i w y r a ż e n ia p r z e k a z a n e g o d o n g - s w it c h ( w t y m p r z y p a d k u w a r t o ś c i z m ie n n e j m a i n C t r l . c u r r e n t T a b ) , t o n a s t ę p u j e w y ś w i e t l e n i e o d p o w i e d n i e g o e le m e n t u . J e ż e li ż a d n a z w a r ­ t o ś c i n g - s w i t c h - w h e n n ie p a s u j e d o w a r t o ś c i t e g o w y r a ż e n ia , t o n a s t ę p u j e w y k o n a n i e p r z y p a d k u n g - s w it c h - d e f a u lt . D y r e k t y w a n g - s w i t c h m a k i l k a c e c h , o k t ó r y c h n a l e ż y p a m ię t a ć : • D y r e k t y w a n g - s w i t c h ł a d u j e s w o j ą tre ś ć , a n a s t ę p n ie n a p o d s t a w ie w a r u n k u w y ł ą c z a z a p o ­ m o c ą k o m e n t a r z a w s z y s t k ie p r z y p a d k i n g - s w it c h - w h e n , k t ó r e n ie s p e ł n ia j ą k r y t e r ió w . Z a t e m n a w e t j e ś li w e w n ą t r z n g - s w i t c h - w h e n u ż y j e m y d y r e k t y w y n g - i n c l u d e , t r e ś ć z o s t a n ie z a ł a d o ­ w a n a t y l k o w t e d y , g d y z o s t a n ie s p e ł n i o n y w a r u n e k . • D y r e k t y w a n g - s w i t c h - w h e n j e s t t r a k t o w a n a j a k o a t r y b u t , w i ę c p o w i n n o s ię jej b e z p o ś r e d ­ n io p r z e k a z a ć w a r to ś ć , a n ie w y r a ż e n ie A n g u la r J S . P o w ie d z m y n p ., że m a m y d y r e k t y w y n g - s w it c h = "m a in C t r l. c u r r e n t T a b " i n g -s w it c h -w h e n = "m a in C t rl.p o s s ib le V a lu e ". W

tym

p r z y p a d k u w a r t o ś ć z m ie n n e j c u r r e n t T a b w k o n t r o l e r z e p o w i n n a b y ć j e d n a k o w a z ł a ń c u c h e m " m a i n C t r l . p o s s i b l e V a l u e " , a n ie z w a r t o ś c ią z m ie n n e j m a i n C t r l. p o s s i b le V a lu e . D y r e k t y w a n g - s w it c h - w h e n n ie r o z p o z n a j e w y r a ż e ń A n g u la r J S .

184

I

Rozdział 11. Dyrektywy

Opcje podstawowe W iesz już, jak i kiedy używać dyrektyw ng-includ e i ng-sw itch, oraz znasz ich wady. Teraz na­ uczysz się tworzyć własne dyrektywy, aby rozwiązać niektóre z opisanych wcześniej problemów. Dyrektywy tworzy się głównie po to, aby: • umożliwić deklarowanie pewnych zadań w kodzie H TM L; • ułatwić wielokrotne wykorzystanie pewnej funkcji bez konieczności kopiowania kodu; • osiągnąć abstrakcję w tym sensie, że użytkownik dyrektywy nie m usi wiedzieć ani rozumieć, jak coś się dzieje, tylko interesuje go wynik. D zięki temu wewnętrzną im plem entację funkcji m ożna zm ienić bez obawy, że naruszy się działający kod. Teraz przyjrzymy się dostępnym opcjom tworzenia dyrektyw. W eźm iem y przykład z podrozdziału o dyrektywie ng-includ e i zam ienim y go w prawidłową dyrektywę do wielokrotnego użytku.

Tworzenie dyrektywy Dyrektywy w systemie AngularJS tworzy się podobnie jak kontrolery, usługi i filtry. Należy użyć funkcji module, która w pierwszym argumencie przyjmuje nazwę tworzonej dyrektywy, a w drugim — nazwę standardowego tablicowego kodu wstrzykiwania zależności, przy czym ostatni element tablicy powinien być funkcją dyrektywy. Powiedzmy, że chcemy zamienić plik stock.h tm l z poprzedniego przykładu w dyrektywę. Zaczniemy od zaplanowania sposobu jego użycia w kodzie H TM L: < d iv s to c k -w id g e t > < / d iv >

Jeśli chcem y m óc deklarować elem enty
jako widżety giełdowe w sposób pokazany powyżej, m usim y zadeklarować dyrektywę następująco: a n g u la r . m o d u le ( 's t o c k M a r k e t A p p ', [ ]) . d ir e c t i v e ( 's t o c k W i d g e t ', [ f u n c t io n ( ) re tu rn {

{

/ / definicja dyrektywy }; }]);

Z definiow aliśm y dyrektywę stockW idget i je j fu nkcję. F u n k cja ta k onfig u ru je dyrektywę przy użyciu tzw. ob iek tu d efin icji dyrektywy i zwraca tę definicję. System AngularJS będzie ją wyko­ rzystywał za każdym razem, gdy napotka naszą dyrektywę w kodzie H TM L. D om yślnie utw orzoną w ten sposób dyrektywę m ożna stosow ać tylko jak o atrybut elem entów H TM L, tzn. m ożem y pisać np.
, ale nie .

Nazwy dyrektyw i atrybutów Jedną z ważnych kwestii jest traktowanie nazw dyrektyw. W języku HTML wielkość znaków nie ma znaczenia. Dlatego system AngularJS podczas translacji nazw atrybutów i dyrektyw z HTML na JavaScript konwertuje je na notację wielbłądzią. W efekcie nazwa stock-widget (lub STOCK-WIDGET albo Stock-Widget) z kodu HTML zmieni się na stockWidget w JavaScripcie.

Opcje podstawowe

|

185

Teraz przyjrzymy się niektórym najczęściej używanym opcjom przy tworzeniu dyrektyw.

Szablon i adres szablonu Pierwszą rzeczą, jaką m ożna zdefiniować w ram ach dyrektywy, jest treść, która m a być wstawiana w m iejscu je j występowania w dokum encie. Służą do tego klucze t e m p la t e i t e m p la t e U r l obiektu definicji dyrektywy (podobnie ja k w trasowaniu). Teraz odtworzymy funkcjonalność przykładu z podrozdziału o dyrektywie n g - i n c l u d e , tylko we własnej dyrektywie, aby m óc deklarować widżety. Plik ap p .js pozostaje bez zmian: // Plik: r11/directive-with-template/app.js a n g u la r . m o d u le ( 's t o c k M a r k e t A p p l , [ ]) . c o n t r o l l e r ( 'M a i n C t r l ', [ f u n c t io n ( ) { v a r s e lf = t h is ; s e lf. s t o c k s = [ {name: 'P ie r w s z y t o w a r ', p r ic e : 100, p r e v io u s : 2 2 0 }, {name: 'D r u g i t o w a r ', p r ic e : 140, p r e v io u s : 120 }, {name: 'T r z e c i t o w a r ', p r ic e : 110, p r e v io u s : 110}, {name: 'C z w a rty t o w a r ', p r ic e : 400, p r e v io u s : 420} ]; s e lf . g e t C h a n g e = f u n c t io n ( s t o c k ) { r e t u r n M a t h . c e i l ( ( ( s t o c k . p r i c e - s t o c k . p r e v io u s ) / s t o c k . p r e v io u s ) * 100 ); }; }]);

Także zawartość pliku sto ck.h tm l pozostaje taka sam a ja k poprzednio: < t i t le > A p li k a c j a g i e łd o w a < / t itle >

186

|

Rozdział 11. Dyrektywy

< h 3 > L is t a towarów