Darmowy fragment publikacji:
Tytuł oryginału: AngularJS
Tłumaczenie: Robert Górczyński
ISBN: 978-83-246-9990-2
© 2014 Helion S.A.
Authorized Polish translation of the English edition AngularJS, ISBN 9781449344856
© 2013 Brad Green and Shyam Seshadri.
This translation is published and sold by permission of O’Reilly Media, Inc., which owns or
controls all rights to publish and sell the same.
All rights reserved. No part of this book may be reproduced or transmitted in any form or by
any means, electronic or mechanical, including photocopying, recording or by any
information storage retrieval system, without permission from the Publisher.
Wszelkie prawa zastrzeżone. Nieautoryzowane rozpowszechnianie całości lub fragmentu
niniejszej publikacji w jakiejkolwiek postaci jest zabronione. Wykonywanie kopii metodą
kserograficzną, fotograficzną, a także kopiowanie książki na nośniku filmowym,
magnetycznym lub innym powoduje naruszenie praw autorskich niniejszej publikacji.
Wszystkie znaki występujące w tekście są zastrzeżonymi znakami firmowymi bądź
towarowymi ich właścicieli.
Autor oraz Wydawnictwo HELION dołożyli wszelkich starań, by zawarte w tej książce
informacje były kompletne i rzetelne. Nie bierze jednak żadnej odpowiedzialności ani
za ich wykorzystanie, ani za związane z tym ewentualne naruszenie praw patentowych
lub autorskich. Wydawnictwo HELION nie ponosi również żadnej odpowiedzialności
za ewentualne szkody wynikłe z wykorzystania informacji zawartych w książce.
Wydawnictwo HELION
ul. Kościuszki 1c, 44-100 GLIWICE
tel. 32 231 22 19, 32 230 98 63
e-mail: helion@helion.pl
WWW: http://helion.pl (księgarnia internetowa, katalog książek)
Pliki z przykładami omawianymi w książce można znaleźć pod adresem:
ftp://ftp.helion.pl/przyklady/angula.zip
Drogi Czytelniku!
Jeżeli chcesz ocenić tę książkę, zajrzyj pod adres
http://helion.pl/user/opinie/angula
Możesz tam wpisać swoje uwagi, spostrzeżenia, recenzję.
Printed in Poland.
• Kup książkę
• Poleć książkę
• Oceń książkę
• Księgarnia internetowa
• Lubię to! » Nasza społeczność
Spis tre(cid:316)ci
Wprowadzenie ...........................................................................................7
8
8
9
Konwencje zastosowane w ksi(cid:241)(cid:276)ce
U(cid:276)ycie przyk(cid:228)adowych kodów
Podzi(cid:246)kowania
Rozdzia(cid:293) 1. Wprowadzenie do AngularJS ................................................11
12
18
21
Koncepcje
Przyk(cid:228)ad — koszyk na zakupy
Co dalej?
Rozdzia(cid:293) 2. Anatomia aplikacji AngularJS ...............................................23
23
24
27
51
55
57
61
63
65
67
Wywo(cid:228)anie AngularJS
Architektura MVC
Szablony i do(cid:228)(cid:241)czanie danych
Organizacja zale(cid:276)no(cid:264)ci za pomoc(cid:241) modu(cid:228)ów
Formatowanie danych za pomoc(cid:241) filtrów
Zmiana widoków za pomoc(cid:241) tras i us(cid:228)ugi $location
Komunikacja z serwerem
U(cid:276)ycie dyrektyw do zmiany elementów drzewa DOM
Weryfikacja danych wej(cid:264)ciowych u(cid:276)ytkownika
Co dalej?
Rozdzia(cid:293) 3. Programowanie w AngularJS ...............................................69
70
73
75
76
79
Organizacja projektu
Narz(cid:246)dzia
Uruchamianie aplikacji
Testowanie w AngularJS
Testy jednostkowe
3
Kup książkęPoleć książkęTesty typu E2E/integracji
Kompilacja
Inne wspania(cid:228)e narz(cid:246)dzia
Narz(cid:246)dzie Yeoman — optymalizacja sposobu pracy
Integracja AngularJS i RequireJS
80
82
84
88
92
Rozdzia(cid:293) 4. Analiza aplikacji AngularJS .................................................101
101
102
105
122
Aplikacja
Relacje mi(cid:246)dzy modelem, kontrolerem i szablonem
Kontrolery, dyrektywy i us(cid:228)ugi
Testy
Rozdzia(cid:293) 5. Komunikacja z serwerami .................................................. 129
129
135
137
143
145
146
147
Komunikacja za pomoc(cid:241) us(cid:228)ugi $http
Testy jednostkowe
Praca z zasobami RESTful
Us(cid:228)uga $q i obietnica
Przechwycenie odpowiedzi
Kwestie bezpiecze(cid:254)stwa
XSRF
Rozdzia(cid:293) 6. Dyrektywy ........................................................................... 149
149
150
170
Dyrektywy i weryfikacja kodu HTML
Ogólny opis API
Co dalej?
Rozdzia(cid:293) 7. Inne kwestie .........................................................................171
171
178
Us(cid:228)uga $location
Metody modu(cid:228)u AngularJS
Komunikacja mi(cid:246)dzy zasi(cid:246)gami
za pomoc(cid:241) $on, $emit i $broadcast
Ciasteczka
Internacjonalizacja i lokalizacja
Oczyszczanie kodu HTML i modu(cid:228) Sanitize
182
184
185
188
4
(cid:95)
Spis tre(cid:316)ci
Kup książkęPoleć książkęRozdzia(cid:293) 8. (cid:315)ci(cid:233)ga i podpowiedzi ...........................................................191
191
196
201
204
207
210
214
Opakowanie kontrolki jQuery datepicker
Lista klubów sportowych — filtrowanie i komunikacja
Przekazywanie plików w aplikacji AngularJS
U(cid:276)ycie biblioteki Socket.IO
Prosta us(cid:228)uga stronicowania
Praca z serwerami i logowaniem
Podsumowanie
Skorowidz ............................................................................................... 216
Spis tre(cid:316)ci
(cid:95)
5
Kup książkęPoleć książkę6
(cid:95)
Spis tre(cid:316)ci
Kup książkęPoleć książkęROZDZIA(cid:292) 4.
Analiza aplikacji AngularJS
W rozdziale 2. przedstawiono pewne najcz(cid:246)(cid:264)ciej u(cid:276)ywane funkcje framewor-
ka AngularJS, natomiast w rozdziale 3. zaj(cid:246)li(cid:264)my si(cid:246) zagadnieniami zwi(cid:241)-
zanymi ze sposobem prowadzenia prac programistycznych. Zamiast konty-
nuowa(cid:232) w(cid:241)tek i podobnie szczegó(cid:228)owo omawia(cid:232) poszczególne funkcje,
w tym rozdziale przejdziemy do ma(cid:228)ej, rzeczywistej aplikacji. Na jej pod-
stawie dowiesz si(cid:246), jak po(cid:228)(cid:241)czy(cid:232) ze sob(cid:241) omówione dot(cid:241)d fragmenty ca(cid:228)o(cid:264)ci
i utworzy(cid:232) rzeczywist(cid:241), dzia(cid:228)aj(cid:241)c(cid:241) aplikacj(cid:246).
Zamiast od razu przedstawia(cid:232) ca(cid:228)(cid:241) aplikacj(cid:246), b(cid:246)dziemy j(cid:241) poznawa(cid:232) w ma-
(cid:228)ych cz(cid:246)(cid:264)ciach, omawia(cid:232) interesuj(cid:241)ce zagadnienia zwi(cid:241)zane z danym frag-
mentem i tym samym powoli budowa(cid:232) kompletn(cid:241) aplikacj(cid:246), która b(cid:246)dzie
gotowa, zanim uko(cid:254)czysz lektur(cid:246) rozdzia(cid:228)u.
Aplikacja
GutHub to prosta aplikacja przeznaczona do zarz(cid:241)dzania przepisami kuli-
narnymi. Zosta(cid:228)a zaprojektowana w taki sposób, aby przechowywa(cid:232) przepi-
sy kulinarne i jednocze(cid:264)nie pokazywa(cid:232) ró(cid:276)ne interesuj(cid:241)ce aspekty aplikacji
AngularJS. Oto cechy charakteryzuj(cid:241)ce tworzon(cid:241) przez nas aplikacj(cid:246):
(cid:120) ma uk(cid:228)ad sk(cid:228)adaj(cid:241)cy si(cid:246) z dwóch kolumn;
(cid:120) pasek nawigacyjny znajduje si(cid:246) po lewej stronie;
(cid:120) pozwala na dodawanie nowych przepisów kulinarnych;
(cid:120) umo(cid:276)liwia przegl(cid:241)danie istniej(cid:241)cych przepisów kulinarnych.
Widok g(cid:228)ówny aplikacji znajduje si(cid:246) po prawej stronie. W zale(cid:276)no(cid:264)ci od
adresu URL ulega ona zmianie i wy(cid:264)wietla list(cid:246) przepisów kulinarnych,
101
Kup książkęPoleć książkęszczegó(cid:228)y dotycz(cid:241)ce konkretnego przepisu lub edytowalny formularz po-
zwalaj(cid:241)cy na dodanie nowego b(cid:241)d(cid:274) na edycj(cid:246) istniej(cid:241)cego. Uruchomion(cid:241)
aplikacj(cid:246) pokazano na rysunku 4.1.
Rysunek 4.1. GutHub, czyli prosta aplikacja przeznaczona do zarz(cid:241)dzania przepisami
kulinarnymi
Ca(cid:228)a aplikacja jest dost(cid:246)pna w repozytorium GitHub na stronie: https://github.
com/shyamseshadri/angularjs-book/tree/master/chapter4.
Relacje mi(cid:253)dzy modelem,
kontrolerem i szablonem
Zanim przejdziemy do omawiania aplikacji, zatrzymajmy si(cid:246) na chwil(cid:246)
i zastanówmy, jak trzy fragmenty aplikacji wspó(cid:228)dzia(cid:228)aj(cid:241) ze sob(cid:241) oraz jak
powinni(cid:264)my je traktowa(cid:232).
Model jest istot(cid:241) aplikacji. Powtórz to zdanie kilkakrotnie. Dzia(cid:228)anie ca(cid:228)ej
aplikacji opiera si(cid:246) na modelu, od którego zale(cid:276)(cid:241): wy(cid:264)wietlany widok, dane
wy(cid:264)wietlane przez widok, zapisywane informacje i dos(cid:228)ownie wszystko.
Warto wi(cid:246)c po(cid:264)wi(cid:246)ci(cid:232) nieco czasu i dok(cid:228)adnie przemy(cid:264)le(cid:232) model — jakie
w(cid:228)a(cid:264)ciwo(cid:264)ci powinien mie(cid:232) obiekt modelu, jak b(cid:246)dzie pobierany z serwera,
jak b(cid:246)dzie zapisywany i tak dalej. Ze wzgl(cid:246)du na to, (cid:276)e uaktualnienie wido-
ku nast(cid:246)puje automatycznie dzi(cid:246)ki u(cid:276)yciu wi(cid:241)zania danych, uwag(cid:246) nale(cid:276)y
skoncentrowa(cid:232) na modelu.
102
(cid:95)
Rozdzia(cid:293) 4. Analiza aplikacji AngularJS
Kup książkęPoleć książkęKontroler zawiera logik(cid:246) biznesow(cid:241) i okre(cid:264)la mi(cid:246)dzy innymi: jak b(cid:246)dzie po-
bierany model, jakie b(cid:246)d(cid:241) rodzaje operacji przeprowadzanych na modelu, ja-
kich informacji widok potrzebuje z modelu, a tak(cid:276)e jak przekszta(cid:228)ci(cid:232) model,
aby uzyska(cid:232) potrzebne dane. Ponadto przeprowadzanie weryfikacji, wy-
konywanie wywo(cid:228)a(cid:254) do serwera, umieszczanie odpowiednich danych w wi-
doku oraz w(cid:228)a(cid:264)ciwie wszystko inne powi(cid:241)zane z wymienionymi dzia(cid:228)ania-
mi to równie(cid:276) aktywno(cid:264)(cid:232) definiowana w kontrolerze.
I na koniec szablon okre(cid:264)la sposób prezentacji modelu oraz interakcji u(cid:276)yt-
kownika z aplikacj(cid:241). Zadania wykonywane przez szablon powinny by(cid:232) ogra-
niczone do wymienionych poni(cid:276)ej:
(cid:120) wy(cid:264)wietlanie modelu;
(cid:120) definiowanie sposobów, na jakie u(cid:276)ytkownik mo(cid:276)e korzysta(cid:232) z aplikacji
— klikni(cid:246)cia, pola danych wej(cid:264)ciowych i tak dalej;
(cid:120) nadawanie stylu aplikacji oraz okre(cid:264)lanie, jak i kiedy pewne elementy
maj(cid:241) by(cid:232) wy(cid:264)wietlane — pokazywanie lub ukrywanie i tak dalej;
(cid:120) filtrowanie i formatowanie danych (zarówno wej(cid:264)ciowych, jak i wyj-
(cid:264)ciowych).
Trzeba pami(cid:246)ta(cid:232), (cid:276)e szablon w AngularJS niekoniecznie jest widokiem
w architekturze MVC (model – widok – kontroler). Zamiast tego widok jest
skompilowan(cid:241) wersj(cid:241) wykonywanego szablonu, rodzajem po(cid:228)(cid:241)czenia sza-
blonu i modelu.
W szablonie nie nale(cid:276)y umieszcza(cid:232) (cid:276)adnego rodzaju logiki biznesowej
ani definiowa(cid:232) zachowania — tego rodzaju dane powinny znajdowa(cid:232) si(cid:246)
w kontrolerze. Zachowanie prostoty szablonów pozwala na w(cid:228)a(cid:264)ciw(cid:241) sepa-
racj(cid:246) obowi(cid:241)zków, a ponadto na przetestowanie wi(cid:246)kszo(cid:264)ci kodu za po-
moc(cid:241) jedynie testów jednostkowych. Szablony powinny by(cid:232) testowane za
pomoc(cid:241) testów scenariuszy.
W tym miejscu móg(cid:228)by(cid:264) zapyta(cid:232): gdzie nale(cid:276)y umieszcza(cid:232) polecenia od-
powiedzialne za modyfikacje obiektowego modelu dokumentu? Operacje
na elementach drzewa DOM nie powinny by(cid:232) definiowane w kontrolerach
lub szablonach. Najlepszym miejscem dla nich s(cid:241) dyrektywy AngularJS,
cho(cid:232) czasami wspomniane operacje mog(cid:241) by(cid:232) stosowane za pomoc(cid:241) us(cid:228)ug,
co pozwala na unikni(cid:246)cie powielania kodu. Przyk(cid:228)ad takiego rozwi(cid:241)zania
w aplikacji GutHub równie(cid:276) zostanie zaprezentowany i omówiony.
Bez zb(cid:246)dnych ceregieli przechodzimy wi(cid:246)c do modelu.
Relacje mi(cid:253)dzy modelem, kontrolerem i szablonem
(cid:95)
103
Kup książkęPoleć książkęModel
W omawianej aplikacji staramy si(cid:246) zachowa(cid:232) maksymaln(cid:241) prostot(cid:246) modelu
— b(cid:246)d(cid:241) to po prostu przepisy kulinarne. Wspomniane przepisy to jedyny
obiekt modelu w ca(cid:228)ej aplikacji. Wszystkie pozosta(cid:228)e komponenty zostan(cid:241)
zbudowane wokó(cid:228) modelu.
Ka(cid:276)dy przepis sk(cid:228)ada si(cid:246) z nast(cid:246)puj(cid:241)cych w(cid:228)a(cid:264)ciwo(cid:264)ci:
(cid:120) identyfikator, je(cid:264)li przepis zosta(cid:228) zapisany na serwerze,
(cid:120) nazwa,
(cid:120) krótki opis,
(cid:120) sposób przygotowania,
(cid:120) informacje o ewentualnym wyró(cid:276)nieniu przepisu,
(cid:120) tablica sk(cid:228)adników podanych w postaci nazwy, ilo(cid:264)ci i jednostki miary.
I to tyle, model jest niezwykle prosty. Pozosta(cid:228)e komponenty aplikacji utwo-
rzymy na podstawie wymienionego modelu. Poni(cid:276)ej przedstawiono przy-
k(cid:228)adowy przepis kulinarny w formacie JSON (przepis ten zosta(cid:228) pokazany
na rysunku 4.1 we wcze(cid:264)niejszej cz(cid:246)(cid:264)ci rozdzia(cid:228)u):
{
id : 1 ,
title : Ciasteczka ,
description : Wyborne, chrupi(cid:200)ce na zewn(cid:200)trz, ci(cid:200)gliwe +
w (cid:258)rodku i ociekaj(cid:200)ce pyszn(cid:200) czekolad(cid:200) +
ciasteczka. Najlepsze w swoim rodzaju. ,
ingredients : [
{
amount : 1 ,
amountUnits : opakowanie ,
ingredientName : Chips Ahoy
}
],
instructions : 1. Kup opakowanie Chips Ahoy\n +
2. Podgrzej ciasteczka w piekarniku\n +
3. Rozsmakuj si(cid:218) w ciep(cid:239)ych ciasteczkach\n +
4. Z innego (cid:283)ród(cid:239)a naucz si(cid:218), jak wypieka(cid:202) wyborne ciasteczka
}
W celu zachowania prostoty przyk(cid:228)adu nie b(cid:246)dziemy zajmowa(cid:232) si(cid:246) serwe-
rem, z którego przepisy kulinarne s(cid:241) pobierane lub w którym s(cid:241) zapisy-
wane. Kod serwera znajduje si(cid:246) w repozytorium w serwisie GitHub, a do
jego uruchomienia s(cid:228)u(cid:276)y polecenie node web-server.js, które trzeba wyda(cid:232)
z poziomu podstawowego katalogu aplikacji GutHub. Przechodzimy teraz
do znacznie bardziej skomplikowanych funkcji interfejsu u(cid:276)ytkownika, jakie
mo(cid:276)na utworzy(cid:232) na postawie naszego prostego modelu.
104
(cid:95)
Rozdzia(cid:293) 4. Analiza aplikacji AngularJS
Kup książkęPoleć książkęKontrolery, dyrektywy i us(cid:293)ugi
Wreszcie mo(cid:276)emy zaj(cid:241)(cid:232) si(cid:246) ciekawszymi aspektami tworzonej przez nas
aplikacji. W pierwszej kolejno(cid:264)ci spojrzymy na kod dyrektyw i us(cid:228)ug i po-
wiemy sobie nieco o sposobie jego dzia(cid:228)ania. Nast(cid:246)pnie przejdziemy do
wielu kontrolerów niezb(cid:246)dnych do zapewnienia prawid(cid:228)owego dzia(cid:228)ania
tworzonej aplikacji.
Us(cid:293)ugi
Poni(cid:276)ej przedstawiono kod (cid:274)ród(cid:228)owy us(cid:228)ug.
// Plik: app/scripts/services/services.js.
var services = angular.module( guthub.services , [ ngResource ]);
services.factory( Recipe , [ $resource , function($resource) {
return $resource( /recipes/:id , {id: @id });
}]);
services.factory( MultiRecipeLoader , [ Recipe , $q , function(Recipe, $q) {
return function() {
var delay = $q.defer();
Recipe.query(function(recipes) {
delay.resolve(recipes);
}, function() {
delay.reject( Nie mo(cid:285)na pobra(cid:202) przepisów kulinarnych. );
});
return delay.promise;
};
}]);
services.factory( RecipeLoader , [ Recipe , $route , $q , function(Recipe,
$route, $q) {
return function() {
var delay = $q.defer();
Recipe.get({id: $route.current.params.recipeId}, function(recipe) {
delay.resolve(recipe);
}, function() {
delay.reject( Nie mo(cid:285)na pobra(cid:202) przepisu + $route.current.params.recipeId);
});
return delay.promise;
};
}]);
Najpierw zajmiemy si(cid:246) us(cid:228)ugami. Nie jest to nasze pierwsze spotkanie
z us(cid:228)ugami — zetkn(cid:246)li(cid:264)my si(cid:246) z nimi ju(cid:276) w rozdziale 2. Tutaj zostan(cid:241) omó-
wione nieco dok(cid:228)adniej.
W przedstawionym pliku znajduj(cid:241) si(cid:246) trzy us(cid:228)ugi AngularJS.
Kontrolery, dyrektywy i us(cid:293)ugi
(cid:95)
105
Kup książkęPoleć książkęIstnieje us(cid:228)uga przepisu kulinarnego, która zwraca tak zwany AngularJS
Resource. Jest to zasób RESTful prowadz(cid:241)cy do serwera RESTful. Wspo-
mniany zasób hermetyzuje dzia(cid:228)aj(cid:241)c(cid:241) na niskim poziomie us(cid:228)ug(cid:246) $http, a tym
samym programista musi utworzy(cid:232) jedynie kod odpowiedzialny za prac(cid:246)
z obiektami.
Za pomoc(cid:241) tylko pojedynczego wiersza kodu (return $resource) oraz oczywi-
(cid:264)cie zale(cid:276)no(cid:264)ci w module guthub.services obiekt Recipe mo(cid:276)e by(cid:232) u(cid:276)yty jako
argument w dowolnym kontrolerze — zostanie wówczas wstrzykni(cid:246)ty do
wskazanego kontrolera. Co wi(cid:246)cej, ka(cid:276)dy obiekt Recipe ma wbudowane
wymienione poni(cid:276)ej metody:
(cid:120) Recipe.get(),
(cid:120) Recipe.save(),
(cid:120) Recipe.query(),
(cid:120) Recipe.remove(),
(cid:120) Recipe.delete().
Je(cid:276)eli zamierzasz u(cid:276)y(cid:232) metody Recipe.delete() i chcesz zapewni(cid:232)
dzia(cid:228)anie aplikacji w przegl(cid:241)darce Internet Explorer, wtedy mu-
sisz u(cid:276)y(cid:232) wywo(cid:228)ania w postaci Recipe[ delete ](). Wynika to
z faktu, (cid:276)e delete jest s(cid:228)owem kluczowym w przegl(cid:241)darce In-
ternet Explorer.
Z wymienionych powy(cid:276)ej metod wszystkie poza query() dzia(cid:228)aj(cid:241) z poje-
dynczym przepisem kulinarnym. Natomiast warto(cid:264)ci(cid:241) zwrotn(cid:241) metody
query() jest domy(cid:264)lnie tablica przepisów kulinarnych.
Wiersz kodu deklaruj(cid:241)cy zasób (return $resource) wykonuje równie(cid:276) kilka
innych u(cid:276)ytecznych zada(cid:254).
1. Zwró(cid:232) uwag(cid:246) na :id w adresie URL wskazanym dla zasobu RESTful.
Wspomniany identyfikator oznacza, (cid:276)e w trakcie wykonywania dowol-
nego zapytania (na przyk(cid:228)ad za pomoc(cid:241) Recipe.get()), je(cid:264)li przeka(cid:276)esz
obiekt wraz z w(cid:228)a(cid:264)ciwo(cid:264)ci(cid:241) id, wówczas jej warto(cid:264)(cid:232) zostanie umiesz-
czona na ko(cid:254)cu adresu URL.
Oznacza to, (cid:276)e wywo(cid:228)anie Recipe.get({id: 15}) faktycznie b(cid:246)dzie wy-
wo(cid:228)aniem do /recipe/15.
2. Móg(cid:228)by(cid:264) zapyta(cid:232) w tym miejscu: co z drugim obiektem, na przyk(cid:228)ad {id:
@id}? Wiersz kodu jest wart tysi(cid:241)ca s(cid:228)ów obja(cid:264)nienia, wi(cid:246)c przejd(cid:274)my
od razu do odpowiedniego przyk(cid:228)adu.
106
(cid:95)
Rozdzia(cid:293) 4. Analiza aplikacji AngularJS
Kup książkęPoleć książkęPrzyjmujemy za(cid:228)o(cid:276)enie, (cid:276)e dost(cid:246)pny jest obiekt Recipe zawieraj(cid:241)cy
wszystkie niezb(cid:246)dne informacje, mi(cid:246)dzy innymi warto(cid:264)(cid:232) id.
Wspomniany obiekt mo(cid:276)na zapisa(cid:232) za pomoc(cid:241) poni(cid:276)szego fragmentu
kodu:
// Przyj(cid:266)to za(cid:225)o(cid:298)enie, (cid:298)e obiekt existingRecipeObj ma wszystkie niezb(cid:266)dne w(cid:225)a(cid:286)ciwo(cid:286)ci,
// w tym id (na przyk(cid:225)ad o warto(cid:286)ci 13).
var recipe = new Recipe(existingRecipeObj);
recipe.$save();
Przedstawiony kod spowoduje wykonanie (cid:276)(cid:241)dania POST do /recipe/13.
Fragment @id powoduje pobranie warto(cid:264)ci w(cid:228)a(cid:264)ciwo(cid:264)ci id obiektu i u(cid:276)y-
cie jej jako parametru id. Takie rozwi(cid:241)zanie przyj(cid:246)to dla wygody — po-
zwala ono zaoszcz(cid:246)dzi(cid:232) kilka wierszy kodu.
W pliku apps/scripts/services/services.js istniej(cid:241) jeszcze dwie inne us(cid:228)ugi.
Obie zaliczaj(cid:241) si(cid:246) do komponentów wczytuj(cid:241)cych: pierwsza (RecipeLoader)
wczytuje pojedynczy przepis, natomiast druga (MultiRecipeLoader) jest prze-
znaczona do wczytywania wszystkich przepisów kulinarnych. Wymienio-
ne us(cid:228)ugi s(cid:241) u(cid:276)ywane podczas konfiguracji tras, a sposób dzia(cid:228)ania tych
us(cid:228)ug jest bardzo podobny i zosta(cid:228) przedstawiony poni(cid:276)ej.
1. Utworzenie obiektu wstrzymanego $q (jest to rodzaj obietnicy frameworka
AngularJS stosowanej w celu (cid:228)(cid:241)czenia funkcji asynchronicznych).
2. Wykonanie wywo(cid:228)ania do serwera.
3. Okre(cid:264)lenie obiektu wstrzymanego, gdy serwer zwraca warto(cid:264)(cid:232).
4. Zwrot obietnicy, która b(cid:246)dzie u(cid:276)ywana przez mechanizm routingu fra-
meworka AngularJS.
Obietnice frameworka AngularJS
Obietnica to interfejs przeznaczony do pracy z obiektami, które s(cid:241) zwra-
cane lub b(cid:246)d(cid:241) wype(cid:228)nione w przysz(cid:228)o(cid:264)ci (w zasadzie s(cid:241) to akcje asyn-
chroniczne). Ogólnie rzecz bior(cid:241)c, na obietnic(cid:246) sk(cid:228)ada si(cid:246) obiekt oraz
funkcja then().
Aby zobaczy(cid:232) zalety obietnic, spójrzmy na przyk(cid:228)ad, w którym konieczne
jest pobranie profilu u(cid:276)ytkownika:
var currentProfile = null;
var username = dowolnaNazwa ;
fetchServerConfig(function(serverConfig) {
fetchUserProfiles(serverConfig.USER_PROFILES, username,
function(profiles) {
currentProfile = profiles.currentProfile;
});
});
Kontrolery, dyrektywy i us(cid:293)ugi
(cid:95)
107
Kup książkęPoleć książkęZ powy(cid:276)szym podej(cid:264)ciem zwi(cid:241)zanych jest kilka problemów.
1. Kod wynikowy b(cid:246)dzie koszmarnie powcinany, zw(cid:228)aszcza je(cid:264)li zajdzie
konieczno(cid:264)(cid:232) po(cid:228)(cid:241)czenia kilku wywo(cid:228)a(cid:254).
2. B(cid:228)(cid:246)dy zg(cid:228)aszane mi(cid:246)dzy wywo(cid:228)aniami zwrotnymi i funkcjami maj(cid:241)
tendencj(cid:246) do znikania, je(cid:276)eli nie zostan(cid:241) r(cid:246)cznie obs(cid:228)u(cid:276)one na ka(cid:276)dym
etapie.
3. W wewn(cid:246)trznym wywo(cid:228)aniu zwrotnym konieczna jest hermetyza-
cja logiki zwi(cid:241)zanej z dzia(cid:228)aniami przeprowadzanymi za pomoc(cid:241)
zmiennej currentProfile bezpo(cid:264)rednio lub za pomoc(cid:241) oddzielnej
funkcji.
Obietnica rozwi(cid:241)zuje wymienione problemy. Zanim si(cid:246) przekonasz, w jaki
sposób, najpierw spójrz na ten sam kod, ale zaimplementowany z u(cid:276)yciem
obietnic:
var currentProfile = fetchServerConfig().then(function(serverConfig) {
return fetchUserProfiles(serverConfig.USER_PROFILES, username);
}).then(function(profiles) {
return profiles.currentProfile;
}, function(error) {
// Obs(cid:225)uga b(cid:225)(cid:266)dów powsta(cid:225)ych w fetchServerConfig()
// lub w fetchUserProfiles().
});
Zwró(cid:232) uwag(cid:246) na zalety nowego rozwi(cid:241)zania.
1. Istnieje mo(cid:276)liwo(cid:264)(cid:232) (cid:228)(cid:241)czenia wywo(cid:228)a(cid:254) funkcji i nie spowoduje to
koszmaru zwi(cid:241)zanego ze stosowaniem wci(cid:246)(cid:232) w kodzie.
2. Masz gwarancj(cid:246), (cid:276)e wywo(cid:228)anie poprzedniej funkcji zostanie zako(cid:254)-
czone, zanim nast(cid:241)pi wywo(cid:228)anie kolejnej funkcji w (cid:228)a(cid:254)cuchu.
3. Ka(cid:276)de wywo(cid:228)anie then() pobiera dwa argumenty (oba to funkcje).
Pierwszy to funkcja wywo(cid:228)ywana w przypadku sukcesu operacji,
natomiast drugi to procedura obs(cid:228)ugi b(cid:228)(cid:246)dów.
4. W przypadku wyst(cid:241)pienia b(cid:228)(cid:246)dów w (cid:228)a(cid:254)cuchu wspomniany b(cid:228)(cid:241)d b(cid:246)-
dzie propagowany przez pozosta(cid:228)e procedury obs(cid:228)ugi b(cid:228)(cid:246)dów. Dla-
tego te(cid:276) b(cid:228)(cid:241)d w dowolnym wywo(cid:228)aniu zwrotnym mo(cid:276)na obs(cid:228)u(cid:276)y(cid:232)
na ko(cid:254)cu.
Móg(cid:228)by(cid:264) zapyta(cid:232): co z wywo(cid:228)aniami resolve() i reject()? Wywo(cid:228)anie
deferred() to we frameworku AngularJS sposób tworzenia obietnic. Z kolei
wywo(cid:228)anie resolve() powoduje spe(cid:228)nienie obietnicy (i wywo(cid:228)anie pro-
cedury obs(cid:228)ugi w przypadku sukcesu operacji), podczas gdy wywo(cid:228)anie
reject powoduje wywo(cid:228)anie procedury obs(cid:228)ugi b(cid:228)(cid:246)dów w obietnicy.
108
(cid:95)
Rozdzia(cid:293) 4. Analiza aplikacji AngularJS
Kup książkęPoleć książkęDo tego zagadnienia powrócimy jeszcze podczas konfiguracji tras.
Dyrektywy
Przechodzimy teraz do dwóch dyrektyw, które b(cid:246)d(cid:241) u(cid:276)ywane w tworzo-
nej tutaj aplikacji.
butterbar
Ta dyrektywa b(cid:246)dzie pokazana lub ukryta w trakcie wczytywania in-
formacji przez stron(cid:246), a tak(cid:276)e po zmianie trasy. Jest po(cid:228)(cid:241)czona z me-
chanizmem zmiany trasy i automatycznie zostaje ukryta lub umiesz-
czona w znaczniku na podstawie stanu strony.
focus
Ta dyrektywa jest u(cid:276)ywana w celu zagwarantowania, (cid:276)e pewne pola
tekstowe (lub elementy) formularza sieciowego s(cid:241) aktywne.
Spójrz na przyk(cid:228)adowy fragment kodu:
// Plik: app/scripts/directives/directives.js.
var directives = angular.module( guthub.directives , []);
directives.directive( butterbar , [ $rootScope , function($rootScope) {
return {
link: function(scope, element, attrs) {
element.addClass( hide );
$rootScope.$on( $routeChangeStart , function() {
element.removeClass( hide );
});
$rootScope.$on( $routeChangeSuccess , function() {
element.addClass( hide );
});
}
};
}]);
directives.directive( focus , function() {
return {
link: function(scope, element, attrs) {
element[0].focus();
}
};
});
Przedstawiona dyrektywa zwraca obiekt wraz z pojedyncz(cid:241) w(cid:228)a(cid:264)ciwo(cid:264)ci(cid:241)
link. Dok(cid:228)adne omówienie tematu tworzenia w(cid:228)asnych dyrektyw znajdziesz
w rozdziale 6., teraz musisz jedynie wiedzie(cid:232) o dwóch rzeczach.
Kontrolery, dyrektywy i us(cid:293)ugi
(cid:95)
109
Kup książkęPoleć książkę 1. Dyrektywy przechodz(cid:241) przez proces sk(cid:228)adaj(cid:241)cy si(cid:246) z dwóch etapów.
Na pierwszym etapie (faza kompilacji) nast(cid:246)puje wyszukanie wszystkich
dyrektyw do(cid:228)(cid:241)czonych do elementu drzewa DOM, a nast(cid:246)pnie ich
przetworzenie. Wszelkie operacje na elementach drzewa DOM s(cid:241)
przeprowadzane na etapie kompilacji. Na ko(cid:254)cu fazy otrzymujesz funk-
cj(cid:246) (cid:228)(cid:241)cz(cid:241)c(cid:241).
2. Na drugim etapie (faza (cid:228)(cid:241)czenia — t(cid:246) faz(cid:246) wcze(cid:264)niej wykorzystali(cid:264)my)
wygenerowany szablon elementów drzewa DOM jest do(cid:228)(cid:241)czany do za-
si(cid:246)gu (scope). Ponadto dodawane s(cid:241) wszelkie komponenty monitoruj(cid:241)ce
lub nas(cid:228)uchuj(cid:241)ce, co oznacza powstanie funkcjonuj(cid:241)cego wi(cid:241)zania mi(cid:246)-
dzy zasi(cid:246)giem scope i elementem. Wszystko, co jest powi(cid:241)zane z zasi(cid:246)giem
scope, zachodzi na etapie (cid:228)(cid:241)czenia.
Co si(cid:246) dzieje w naszej dyrektywie? Zajrzyjmy do niej i przekonajmy si(cid:246).
Dyrektywa butterbar mo(cid:276)e by(cid:232) u(cid:276)ywana w nast(cid:246)puj(cid:241)cy sposób:
div butterbar Komunikat informuj(cid:200)cy o wczytywaniu... /div
Dzia(cid:228)anie dyrektywy polega na ukryciu elementu oraz dodaniu dwóch
komponentów monitoruj(cid:241)cych zasi(cid:246)g g(cid:228)ówny (scope). Za ka(cid:276)dym razem,
gdy rozpoczyna si(cid:246) zmiana trasy, nast(cid:246)puje pokazanie elementu (przez
zmian(cid:246) jego klasy), a po zako(cid:254)czonej powodzeniem zmianie trasy mamy
ponowne ukrycie dyrektywy butterbar.
Interesuj(cid:241)c(cid:241) cech(cid:241), na któr(cid:241) warto tutaj zwróci(cid:232) uwag(cid:246), jest sposób wstrzyk-
ni(cid:246)cia $rootScope do dyrektywy. Wszystkie dyrektywy maj(cid:241) bezpo(cid:264)rednie
powi(cid:241)zanie z systemem wstrzykiwania zale(cid:276)no(cid:264)ci w AngularJS, co pozwala
na wstrzykiwanie do nich us(cid:228)ug oraz innych niezb(cid:246)dnych komponentów.
Ostatnia kwestia warta uwagi to API przeznaczone do pracy z elementem.
Programi(cid:264)ci przyzwyczajeni do biblioteki jQuery b(cid:246)d(cid:241) szcz(cid:246)(cid:264)liwi, wiedz(cid:241)c,
(cid:276)e zastosowanie ma doskonale znana im sk(cid:228)adnia (addClass, removeClass).
Framework AngularJS implementuje pewien podzbiór wywo(cid:228)a(cid:254) jQuery,
a wi(cid:246)c biblioteka jQuery stanowi opcjonaln(cid:241) zale(cid:276)no(cid:264)(cid:232) dla ka(cid:276)dego projektu
AngularJS. Je(cid:276)eli w projekcie chcesz wykorzysta(cid:232) pe(cid:228)ni(cid:246) mo(cid:276)liwo(cid:264)ci ofe-
rowanych przez jQuery, wtedy powiniene(cid:264) wiedzie(cid:232), (cid:276)e AngularJS u(cid:276)ywa
jej zamiast wbudowanej implementacji.
Druga dyrektywa (focus) jest znacznie prostsza. Jej dzia(cid:228)anie polega na wywo-
(cid:228)aniu metody focus() dla bie(cid:276)(cid:241)cego elementu. Mo(cid:276)na j(cid:241) wywo(cid:228)a(cid:232) przez doda-
nie atrybutu focus do dowolnego elementu danych wej(cid:264)ciowych, na przyk(cid:228)ad:
input type= text focus /input
Podczas wczytywania strony element automatycznie jest aktywny.
110
(cid:95)
Rozdzia(cid:293) 4. Analiza aplikacji AngularJS
Kup książkęPoleć książkęKontrolery
Po zaprezentowaniu dyrektyw i us(cid:228)ug mo(cid:276)esz wreszcie przej(cid:264)(cid:232) do kon-
trolerów, których w naszej aplikacji mamy pi(cid:246)(cid:232). Wszystkie zosta(cid:228)y zdefi-
niowane w pojedynczym pliku (app/scripts/controllers/controllers.js), ale omó-
wimy je tutaj pojedynczo. Przechodzimy wi(cid:246)c do pierwszego kontrolera
(ListCtrl), odpowiedzialnego za wy(cid:264)wietlenie listy wszystkich przepisów
kulinarnych przechowywanych w systemie.
app.controller( ListCtrl , [ $scope , recipes , function($scope, recipes) {
$scope.recipes = recipes;
}]);
Zwró(cid:232) uwag(cid:246) na jedn(cid:241) bardzo wa(cid:276)n(cid:241) kwesti(cid:246) w przypadku omawianego
kontrolera: w konstruktorze nie zawiera on (cid:276)adnego kodu dotycz(cid:241)cego
nawi(cid:241)zania po(cid:228)(cid:241)czenia z serwerem i pobrania przepisów kulinarnych.
Zamiast tego kod zajmuje si(cid:246) obs(cid:228)ug(cid:241) wcze(cid:264)niej pobranych przepisów. By(cid:232)
mo(cid:276)e zastanawiasz si(cid:246), jak to zosta(cid:228)o zrobione. Có(cid:276), dok(cid:228)adn(cid:241) odpowied(cid:274)
poznasz w sekcji po(cid:264)wi(cid:246)conej routingowi, ale ju(cid:276) teraz mo(cid:276)emy powie-
dzie(cid:232), (cid:276)e wi(cid:241)(cid:276)e si(cid:246) to z us(cid:228)ug(cid:241) MultiRecipeLoader. Po prostu o tym pami(cid:246)taj.
Po zapoznaniu si(cid:246) z kontrolerem ListCtrl zobaczysz, (cid:276)e pozosta(cid:228)e s(cid:241) ca(cid:228)-
kiem podobne do omówionego. Mimo wszystko zaprezentujemy je po
kolei, wskazuj(cid:241)c przy tym interesuj(cid:241)ce aspekty:
app.controller( ViewCtrl , [ $scope , $location , recipe ,
function($scope, $location, recipe) {
$scope.recipe = recipe;
$scope.edit = function() {
$location.path( /edit/ + recipe.id);
};
}]);
Interesuj(cid:241)cym aspektem kontrolera ViewCtrl jest funkcja edycji udost(cid:246)p-
niana obiektowi scope. Zamiast pokazywa(cid:232) i ukrywa(cid:232) pola lub stosowa(cid:232)
podobne rozwi(cid:241)zanie, kontroler wykorzystuje framework AngularJS i zleca
mu wykonanie najtrudniejszych zada(cid:254) (powiniene(cid:264) stosowa(cid:232) takie samo
podej(cid:264)cie!). Funkcja edit() po prostu zmienia adres URL na odpowiednik
przepisu kulinarnego, a AngularJS zajmuje si(cid:246) reszt(cid:241). Ponadto framework
wykrywa zmian(cid:246) adresu URL i wczytuje odpowiedni widok (w trybie
edycji b(cid:246)dzie to po prostu dany przepis kulinarny). Wspaniale!
Przechodzimy teraz do kontrolera EditCtrl:
app.controller( EditCtrl , [ $scope , $location , recipe ,
function($scope, $location, recipe) {
$scope.recipe = recipe;
Kontrolery, dyrektywy i us(cid:293)ugi
(cid:95)
111
Kup książkęPoleć książkę $scope.save = function() {
$scope.recipe.$save(function(recipe) {
$location.path( /view/ + recipe.id);
});
};
$scope.remove = function() {
delete $scope.recipe;
$location.path( / );
};
}]);
W tym kontrolerze nowo(cid:264)ci(cid:241) s(cid:241) metody save() i remove(), które EditCtrl
udost(cid:246)pnia obiektowi scope.
Metoda save() obiektu scope dzia(cid:228)a zgodnie z oczekiwaniami. Zapisuje bie-
(cid:276)(cid:241)cy przepis kulinarny, a po zako(cid:254)czeniu operacji zapisu przekierowuje
u(cid:276)ytkownika do widoku wy(cid:264)wietlaj(cid:241)cego ten sam przepis. Funkcja wywo(cid:228)a-
nia zwrotnego jest u(cid:276)yteczna, poniewa(cid:276) pozwala na przeprowadzenie pew-
nych operacji po zapisie.
Istniej(cid:241) dwa sposoby zapisania przepisu. Jeden z nich zosta(cid:228) przedstawio-
ny w kodzie i polega na wywo(cid:228)aniu funkcji $scope.recipe.$save(). Takie
rozwi(cid:241)zanie jest mo(cid:276)liwe tylko dlatego, (cid:276)e recipe jest obiektem zasobu zwró-
conego przez RecipeLoader.
Natomiast drugi sposób zapisu to wywo(cid:228)anie:
Recipe.save(recipe);
Metoda remove() równie(cid:276) nale(cid:276)y do prostych, a jej dzia(cid:228)anie polega na
usuni(cid:246)ciu przepisu z obiektu scope oraz przekierowaniu u(cid:276)ytkownika na
stron(cid:246) g(cid:228)ówn(cid:241). Zwró(cid:232) uwag(cid:246), (cid:276)e nie powoduje to rzeczywistego usuni(cid:246)cia
przepisu kulinarnego z serwera. Wykonanie dodatkowego wywo(cid:228)ania nie
powinno by(cid:232) zbyt trudne.
Kolejny kontroler nosi nazw(cid:246) NewCtrl:
app.controller( NewCtrl , [ $scope , $location , Recipe ,
function($scope, $location, Recipe) {
$scope.recipe = new Recipe({
ingredients: [ {} ]
});
$scope.save = function() {
$scope.recipe.$save(function(recipe) {
$location.path( /view/ + recipe.id);
});
};
}]);
112
(cid:95)
Rozdzia(cid:293) 4. Analiza aplikacji AngularJS
Kup książkęPoleć książkęTen kontroler jest niemal dok(cid:228)adnie taki sam jak EditCtrl (jako (cid:232)wiczenie
móg(cid:228)by(cid:264) oba wymienione kontrolery po(cid:228)(cid:241)czy(cid:232) w jeden). Jedyna ró(cid:276)nica
polega na tym, (cid:276)e pierwszym krokiem w dzia(cid:228)aniu kontrolera NewCtrl jest
utworzenie nowego przepisu kulinarnego (wspomniany przepis to zasób,
a wi(cid:246)c kontroler ma funkcj(cid:246) save()). Ca(cid:228)a pozosta(cid:228)a funkcjonalno(cid:264)(cid:232) nie
ulega zmianie.
Ostatni kontroler to IngredientsCtrl. Jest to kontroler specjalny, ale zanim
przejdziemy do jego omówienia, spójrz na tworz(cid:241)cy go kod:
app.controller( IngredientsCtrl , [ $scope , function($scope) {
$scope.addIngredient = function() {
var ingredients = $scope.recipe.ingredients;
ingredients[ingredients.length] = {};
};
$scope.removeIngredient = function(index) {
$scope.recipe.ingredients.splice(index, 1);
};
}]);
Wszystkie przedstawione dot(cid:241)d kontrolery s(cid:241) po(cid:228)(cid:241)czone z okre(cid:264)lonymi wi-
dokami w interfejsie u(cid:276)ytkownika. Pod tym wzgl(cid:246)dem kontroler Ingredient-
sCtrl dzia(cid:228)a nieco inaczej. To po prostu kontroler potomny u(cid:276)ywany do
edycji stron i hermetyzacji pewnych funkcji niepotrzebnych na ogólnym
poziomie. Warto w tym miejscu wspomnie(cid:232) o pewnej interesuj(cid:241)cej kwestii.
Skoro to kontroler potomny, dziedziczy obiekt scope po kontrolerze nad-
rz(cid:246)dnym (w omawianym przyk(cid:228)adzie jest to kontroler EditCtrl lub NewCtrl).
Dlatego te(cid:276) uzyskanie dost(cid:246)pu do obiektu $scope.recipe odbywa si(cid:246) z po-
ziomu kontrolera nadrz(cid:246)dnego.
Sam kod kontrolera nie zawiera nic szczególnie interesuj(cid:241)cego lub unikalnego.
Dodaje kilka nowych sk(cid:228)adników do tablicy sk(cid:228)adników przepisu kulinarnego
lub te(cid:276) usuwa okre(cid:264)lony sk(cid:228)adnik z listy.
W ten sposób omówili(cid:264)my wszystkie kontrolery. Jedyny fragment kodu
JavaScript, jaki pozosta(cid:228) do przeanalizowania, dotyczy konfiguracji routingu:
// Plik: app/scripts/controllers/controllers.js.
var app = angular.module( guthub ,
[ guthub.directives , guthub.services ]);
app.config([ $routeProvider , function($routeProvider) {
$routeProvider.
when( / , {
controller: ListCtrl ,
resolve: {
recipes: function(MultiRecipeLoader) {
return MultiRecipeLoader();
}
},
Kontrolery, dyrektywy i us(cid:293)ugi
(cid:95)
113
Kup książkęPoleć książkę templateUrl: /views/list.html
}).when( /edit/:recipeId , {
controller: EditCtrl ,
resolve: {
recipe: function(RecipeLoader) {
return RecipeLoader();
}
},
templateUrl: /views/recipeForm.html
}).when( /view/:recipeId , {
controller: ViewCtrl ,
resolve: {
recipe: function(RecipeLoader) {
return RecipeLoader();
}
},
templateUrl: /views/viewRecipe.html
}).when( /new , {
controller: NewCtrl ,
templateUrl: /views/recipeForm.html
}).otherwise({redirectTo: / });
}]);
Zgodnie z wcze(cid:264)niejsz(cid:241) obietnic(cid:241) docieramy do miejsca, w którym u(cid:276)y-
wana jest funkcja resolve(). W poprzednim fragmencie kodu skonfigu-
rowano modu(cid:228) guthub AngularJS, a tak(cid:276)e trasy i szablony wykorzystywane
w aplikacji.
Kod (cid:228)(cid:241)czy dyrektywy z utworzonymi przez nas us(cid:228)ugami, a nast(cid:246)pnie
wskazuje ró(cid:276)ne trasy, które b(cid:246)d(cid:241) stosowane w aplikacji.
Dla ka(cid:276)dej trasy definiowany jest adres URL, kontroler odpowiedzialny za ob-
s(cid:228)ug(cid:246) danego adresu, wczytywany szablon, a tak(cid:276)e (wreszcie) obiekt resolve.
Obiekt resolve nakazuje frameworkowi AngularJS spe(cid:228)nienie wymaga(cid:254)
ka(cid:276)dego klucza, zanim trasa b(cid:246)dzie mog(cid:228)a zosta(cid:232) u(cid:276)yta do wy(cid:264)wietlenia
odpowiedniego widoku u(cid:276)ytkownikowi. Zadanie aplikacji polega na wczy-
taniu wszystkich przepisów kulinarnych (lub tylko wskazanego), a serwer
ma udzieli(cid:232) odpowiedzi przed wy(cid:264)wietleniem strony u(cid:276)ytkownikowi. Do-
stawc(cid:246) tras informujemy wi(cid:246)c o posiadaniu przepisów kulinarnych (lub
przepisu), a nast(cid:246)pnie podajemy mu sposób, w jaki maj(cid:241) by(cid:232) pobrane dane.
W trakcie wykonywania operacji pobierania danych wykorzystywane s(cid:241)
dwie us(cid:228)ugi (MultiRecipeLoader i RecipeLoader) zdefiniowane na pocz(cid:241)tku
tworzenia aplikacji. Framework AngularJS zosta(cid:228) do(cid:264)(cid:232) sprytnie zaprojek-
towany — je(cid:276)eli warto(cid:264)ci(cid:241) zwrotn(cid:241) funkcji resolve() b(cid:246)dzie obietnica An-
gularJS, wtedy framework poczeka na spe(cid:228)nienie wspomnianej obietnicy
przed przej(cid:264)ciem dalej. Oznacza to konieczno(cid:264)(cid:232) zaczekania, a(cid:276) serwer udzieli
odpowiedzi.
114
(cid:95)
Rozdzia(cid:293) 4. Analiza aplikacji AngularJS
Kup książkęPoleć książkęWynik jest w postaci argumentów (o nazwach parametrów b(cid:246)d(cid:241)cych po-
lami obiektu) przekazywany konstruktorowi.
Na ko(cid:254)cu funkcja otherwise() wskazuje domy(cid:264)lny adres URL dla przekie-
rowania, je(cid:264)li nie nast(cid:241)pi dopasowanie (cid:276)adnej trasy.
By(cid:232) mo(cid:276)e zauwa(cid:276)y(cid:228)e(cid:264), (cid:276)e kontrolery EditCtrl i NewCtrl korzy-
staj(cid:241) z tego samego szablonu, czyli views/recipeForm.html. Co si(cid:246) tutaj
dzieje? Po prostu ponownie wykorzystali(cid:264)my szablon przezna-
czony do edycji przepisu kulinarnego. Szablon wy(cid:264)wietla ró(cid:276)ne
elementy w zale(cid:276)no(cid:264)ci od wywo(cid:228)anego kontrolera.
Po zako(cid:254)czeniu omawiania kontrolerów mo(cid:276)emy przej(cid:264)(cid:232) do szablonów.
Zobaczysz, w jaki sposób wymienione kontrolery zosta(cid:228)y powi(cid:241)zane z sza-
blonami, a tak(cid:276)e dowiesz si(cid:246), jak zarz(cid:241)dza(cid:232) danymi, które s(cid:241) wy(cid:264)wietlane
u(cid:276)ytkownikowi.
Szablony
Rozpoczynamy od zapoznania si(cid:246) z najbardziej zewn(cid:246)trznym, g(cid:228)ównym
szablonem zdefiniowanym w pliku index.html. Stanowi on podstaw(cid:246) dla
naszej aplikacji sk(cid:228)adaj(cid:241)cej si(cid:246) z pojedynczej strony, a wszystkie pozosta(cid:228)e
widoki s(cid:241) wczytywane w kontek(cid:264)cie omawianego tutaj szablonu:
!DOCTYPE html
html lang= pl ng-app= guthub
head
title GutHub - tworzenie przepisów kulinarnych i dzielenie si(cid:218) nimi /title
script src= scripts/vendor/angular.min.js /script
script src= scripts/vendor/angular-resource.min.js /script
script src= scripts/directives/directives.js /script
script src= scripts/services/services.js /script
script src= scripts/controllers/controllers.js /script
link href= styles/bootstrap.css rel= stylesheet
link href= styles/guthub.css rel= stylesheet
/head
body
header
h1 GutHub /h1
/header
div butterbar Wczytywanie... /div
div class= container-fluid
div class= row-fluid
div class= span2
!-- Pasek boczny. --
div id= focus a href= #/new Nowy przepis /a /div
Kontrolery, dyrektywy i us(cid:293)ugi
(cid:95)
115
Kup książkęPoleć książkę div a href= #/ Lista przepisów /a /div
/div
div class= span10
div ng-view /div
/div
/div
/div
/body
/html
W przedstawionym szablonie istnieje pi(cid:246)(cid:232) elementów, na które warto zwró-
ci(cid:232) uwag(cid:246). Wi(cid:246)kszo(cid:264)(cid:232) z nich mia(cid:228)e(cid:264) okazj(cid:246) pozna(cid:232) w rozdziale 2. Wspo-
mniane elementy omówimy po kolei.
ng-app
Ustawienie modu(cid:228)u dla aplikacji GutHub. Jest to dok(cid:228)adnie ten sam
modu(cid:228), który wykorzystali(cid:264)my we funkcji angular.module(). W ten sposób
framework AngularJS wie, jak wszystko ma zosta(cid:232) ze sob(cid:241) po(cid:228)(cid:241)czone.
script znacznik
W tym miejscu nast(cid:246)puje wczytanie AngularJS w aplikacji. Framework
trzeba wczyta(cid:232) przed wszystkimi plikami JavaScript, które go u(cid:276)ywa-
j(cid:241). W idealnej sytuacji znaczniki odpowiedzialne za wczytywanie
skryptów JavaScript powinny znajdowa(cid:232) si(cid:246) na ko(cid:254)cu pliku szablonu.
butterbar
Aha! To pierwsze u(cid:276)ycie naszej w(cid:228)asnej dyrektywy. Ta dyrektywa zo-
sta(cid:228)a zdefiniowana wcze(cid:264)niej i chcemy j(cid:241) wykorzysta(cid:232) wraz z elemen-
tem wy(cid:264)wietlanym podczas zmiany trasy. Po zako(cid:254)czeniu powodze-
niem operacji zmiany trasy element powi(cid:241)zany z dyrektyw(cid:241) butterbar
powinien zosta(cid:232) ukryty. Dyrektywa powoduje wy(cid:264)wietlenie tekstu
(w omawianym przypadku jest to nudny komunikat Wczytywanie...), gdy
zachodzi potrzeba.
(cid:146)(cid:200)cza href warto(cid:258)ci
To (cid:228)(cid:241)cza href do ró(cid:276)nych stron naszej aplikacji sk(cid:228)adaj(cid:241)cej si(cid:246) z poje-
dynczej strony. Zwró(cid:232) uwag(cid:246) na u(cid:276)ycie znaku # gwarantuj(cid:241)cego, (cid:276)e
strona nie zostanie ponownie wczytana. Adresy s(cid:241) podawane wzgl(cid:246)-
dem strony bie(cid:276)(cid:241)cej. Framework AngularJS monitoruje wspomniane
adresy URL (dopóki strona nie zostanie ponownie wczytana) i wykonuje
ca(cid:228)(cid:241) prac(cid:246) zwi(cid:241)zan(cid:241) z ich obs(cid:228)ug(cid:241) (w rzeczywisto(cid:264)ci jest to bardzo
nudne zarz(cid:241)dzanie trasami zdefiniowane przez nas wcze(cid:264)niej wraz
z trasami), gdy zachodzi potrzeba.
116
(cid:95)
Rozdzia(cid:293) 4. Analiza aplikacji AngularJS
Kup książkęPoleć książkęng-view
W tym miejscu wykonywana jest pozosta(cid:228)a cz(cid:246)(cid:264)(cid:232) pracy. Wcze(cid:264)niej we
fragmencie rozdzia(cid:228)u po(cid:264)wi(cid:246)conym kontrolerom zdefiniowali(cid:264)my trasy.
Cz(cid:246)(cid:264)ci(cid:241) definicji jest adres URL trasy, powi(cid:241)zany z ni(cid:241) kontroler i sza-
blon. Kiedy framework AngularJS wykryje zmian(cid:246) trasy, wtedy na-
st(cid:246)puje wczytanie szablonu, do(cid:228)(cid:241)czenie do niego kontrolera oraz za-
st(cid:241)pienie elementu ng-view zawarto(cid:264)ci(cid:241) szablonu.
Jedyn(cid:241) rzecz(cid:241) rzucaj(cid:241)c(cid:241) si(cid:246) w oczy jest brak znacznika ng-controller. Wi(cid:246)k-
szo(cid:264)(cid:232) aplikacji zawiera pewnego rodzaju kontroler MainController powi(cid:241)zany
z szablonem g(cid:228)ównym. Najcz(cid:246)stszym miejscem jego podania jest znacznik
body . W omawianej aplikacji nie u(cid:276)ywamy wspomnianego znacznika,
poniewa(cid:276) ca(cid:228)y szablon g(cid:228)ówny nie zawiera tre(cid:264)ci AngularJS wymagaj(cid:241)cej
odwo(cid:228)ania do obiektu scope.
Spójrzmy teraz na szablony powi(cid:241)zane z poszczególnymi kontrolerami. Na
pocz(cid:241)tek przygl(cid:241)damy si(cid:246) szablonowi wy(cid:264)wietlaj(cid:241)cemu list(cid:246) przepisów
kulinarnych:
!-- Plik: chapter4/guthub/app/views/list.html. --
h3 Lista przepisów /h3
ul class= recipes
li ng-repeat= recipe in recipes
div a ng-href= #/view/{{recipe.id}} {{recipe.title}} /a /div
/li
/ul
To naprawd(cid:246) bardzo nudny szablon. Znajduj(cid:241) si(cid:246) tutaj jedynie dwa intere-
suj(cid:241)ce punkty. Pierwszy to standardowy sposób u(cid:276)ycia znacznika ng-repeat.
Zadanie wymienionego znacznika polega na pobraniu wszystkich przepisów
z obiektu scope, a nast(cid:246)pnie ich wy(cid:264)wietleniu.
Drugi interesuj(cid:241)cy punkt to u(cid:276)ycie znacznika ng-href zamiast href. Ma to
na celu unikni(cid:246)cie wygenerowania nieprawid(cid:228)owego (cid:228)(cid:241)cza podczas wczyty-
wania frameworka AngularJS. Znacznik ng-href gwarantuje, (cid:276)e w (cid:276)adnej
chwili u(cid:276)ytkownikowi nie zostanie wy(cid:264)wietlony nieprawid(cid:228)owy znacznik.
Wymienionego znacznika powiniene(cid:264) u(cid:276)ywa(cid:232) zawsze, gdy adresy URL s(cid:241)
dynamiczne, a nie statyczne.
By(cid:232) mo(cid:276)e zadajesz sobie pytanie: gdzie podzia(cid:228) si(cid:246) kontroler? Nie mamy
zdefiniowanego znacznika ng-controller i tak naprawd(cid:246) nie ma zdefinio-
wanego kontrolera g(cid:228)ównego. W tym miejscu do gry wchodzi mapowanie
tras. Mo(cid:276)e pami(cid:246)tasz (mówili(cid:264)my o tym kilka stron wcze(cid:264)niej), (cid:276)e trasa /
powoduje przekierowanie do wy(cid:264)wietlaj(cid:241)cego list(cid:246) przepisów kulinarnych
szablonu, któremu przypisano kontroler ListCtrl. Dlatego te(cid:276) wszelkie od-
niesienia do zmiennych pozostaj(cid:241) w zasi(cid:246)gu wymienionego kontrolera.
Kontrolery, dyrektywy i us(cid:293)ugi
(cid:95)
117
Kup książkęPoleć książkęTeraz przechodzimy do znacznie ciekawszego szablonu, czyli odpowiedzial-
nego za wy(cid:264)wietlenie przepisu.
!-- Plik: chapter4/guthub/app/views/viewRecipe.html. --
h2 {{recipe.title}} /h2
div {{recipe.description}} /div
h3 Sk(cid:239)adniki /h3
span ng-show= recipe.ingredients.length == 0 Brak sk(cid:239)adników /span
ul class= unstyled ng-hide= recipe.ingredients.length == 0
li ng-repeat= ingredient in recipe.ingredients
span {{ingredient.amount}} /span
span {{ingredient.amountUnits}} /span
span {{ingredient.ingredientName}} /span
/li
/ul
h3 Sposób przygotowania /h3
div {{recipe.instructions}} /div
form ng-submit= edit() class= form-horizontal
div class= form-actions
button class= btn btn-primary Edycja /button
/div
/form
To kolejny ma(cid:228)y, przydatny szablon. Warto zwróci(cid:232) uwag(cid:246) na dwa punkty
powy(cid:276)szego szablonu, cho(cid:232) niekoniecznie w kolejno(cid:264)ci ich wymienienia.
Pierwszy to ca(cid:228)kiem standardowy sposób u(cid:276)ycia dyrektywy ng-repeat.
Przepisy kulinarne znajduj(cid:241) si(cid:246) w zasi(cid:246)gu kontrolera ViewCtrl wczytanego
przez funkcj(cid:246) resolve() przed wy(cid:264)wietleniem strony u(cid:276)ytkownikowi. Dzi(cid:246)ki
temu gwarantujemy prawid(cid:228)owe dzia(cid:228)anie strony, gdy zostaje wy(cid:264)wietlona.
Drugi punkt to u(cid:276)ycie dyrektywy ng-submit w formularzu. Wymieniona
dyrektywa oznacza, (cid:276)e wys(cid:228)anie formularza spowoduje wywo(cid:228)anie funkcji
edit() obiektu scope. Wys(cid:228)anie formularza nast(cid:246)puje, gdy klikni(cid:246)ty b(cid:246)dzie
przycisk niepowi(cid:241)zany z (cid:276)adn(cid:241) funkcj(cid:241) (w omawianym przypadku to
przycisk Edycja). I znów dzia(cid:228)anie frameworka AngularJS zosta(cid:228)o zaprojek-
towane bardzo sprytnie — potrafi on prawid(cid:228)owo ustali(cid:232) zasi(cid:246)g, do którego
ma si(cid:246) odwo(cid:228)ywa(cid:232) (na przyk(cid:228)ad: modu(cid:228)u, trasy lub kontrolera), i wywo(cid:228)a(cid:232)
odpowiedni(cid:241) metod(cid:246) we w(cid:228)a(cid:264)ciwym czasie.
Teraz mo(cid:276)emy przej(cid:264)(cid:232) do ostatniego (i prawdopodobnie najbardziej skom-
plikowanego) szablonu, czyli formularza pozwalaj(cid:241)cego na dodanie lub edy-
cj(cid:246) przepisu kulinarnego.
118
(cid:95)
Rozdzia(cid:293) 4. Analiza aplikacji AngularJS
Kup książkęPoleć książkę !-- Plik: chapter4/guthub/app/views/recipeForm.html. --
h2 Edycja przepisu /h2
form name= recipeForm ng-submit= save() class= form-horizontal
div class= control-group
label class= control-label for= title Nazwa: /label
div class= controls
input ng-model= recipe.title class= input-xlarge id= title focus required
/div
/div
div class= control-group
label class= control-label for= description Opis: /label
div class= controls
textarea ng-model= recipe.description class= input-xlarge
id= description /textarea
/div
/div
div class= control-group
label class= control-label for= ingredients Sk(cid:239)adniki: /label
div class= controls
ul id= ingredients class= unstyled ng-controller= IngredientsCtrl
li ng-repeat= ingredient in recipe.ingredients
input ng-model= ingredient.amount class= input-mini
input ng-model= ingredient.amountUnits class= input-small
input ng-model= ingredient.ingredientName
button type= button class= btn btn-mini
ng-click= removeIngredient($index) i class= icon-minus-sign /i
Usu(cid:241) /button
/li
button type= button class= btn btn-mini ng-click= addIngredient()
i class= icon-plus-sign /i Dodaj /button
/ul
/div
/div
div class= control-group
label class= control-label for= instructions Sposób
przygotowania: /label
div class= controls
textarea ng-model= recipe.instructions class= input-xxlarge
id= instructions /textarea
/div
/div
div class= form-actions
button class= btn btn-primary ng-disabled= recipeForm.$invalid Zapisz
/button
button type= button ng-click= remove() ng-show= !recipe.id class= btn
Usu(cid:241) /button
/div
/form
Kontrolery, dyrektywy i us(cid:293)ugi
(cid:95)
119
Kup książkęPoleć książkęNie panikuj! Wygl(cid:241)da na to, (cid:276)e szablon zawiera ca(cid:228)kiem spor(cid:241) ilo(cid:264)(cid:232) kodu,
i faktycznie tak jest. Jednak po rzeczywistym zag(cid:228)(cid:246)bieniu si(cid:246) we(cid:254) mo(cid:276)na
si(cid:246) przekona(cid:232), (cid:276)e kod nie jest skomplikowany. Tak naprawd(cid:246) to prosta,
powtarzaj(cid:241)ca si(cid:246) struktura, pokazuj(cid:241)ca, jak edytowalne pola tekstowe zo-
sta(cid:228)y zastosowane w formularzu przeznaczonym do edycji przepisów ku-
linarnych.
(cid:120) W pierwszym polu tekstowym (title) zosta(cid:228)a umieszczona dyrektywa
focus. Dzi(cid:246)ki temu po przej(cid:264)ciu na t(cid:246) stron(cid:246) wskazane pole zostanie
wybrane, a u(cid:276)ytkownik b(cid:246)dzie móg(cid:228) natychmiast rozpocz(cid:241)(cid:232) wprowa-
dzanie danych wej(cid:264)ciowych.
(cid:120) Dyrektywa ng-submit jest u(cid:276)yta w bardzo podobny sposób jak w po-
przednim przyk(cid:228)adzie, a wi(cid:246)c nie b(cid:246)dziemy jej tutaj dok(cid:228)adnie oma-
wia(cid:232). Warto wiedzie(cid:232), (cid:276)e powoduje zapisanie stanu przepisu kulinarne-
go i wskazuje koniec procesu edycji. Ponadto jest powi(cid:241)zana z funkcj(cid:241)
save() zdefiniowan(cid:241) w kontrolerze EditCtrl.
(cid:120) Dyrektywa ng-model s(cid:228)u(cid:276)y do po(cid:228)(cid:241)czenia ró(cid:276)nych pól tekstowych for-
mularza sieciowego z polami modelu.
(cid:120) Jednym z najbardziej interesuj(cid:241)cych aspektów omawianej strony jest
umieszczona w cz(cid:246)(cid:264)ci po(cid:264)wi(cid:246)conej li(cid:264)cie sk(cid:228)adników dyrektywa ng-
controller, której naprawd(cid:246) warto po(cid:264)wi(cid:246)ci(cid:232) nieco uwagi i spróbowa(cid:232)
w pe(cid:228)ni zrozumie(cid:232) sposób jej dzia(cid:228)ania. Zobaczmy wi(cid:246)c, co si(cid:246) tutaj
dzieje.
Lista sk(cid:228)adników jest wy(cid:264)wietlana, a zawieraj(cid:241)cy j(cid:241) znacznik jest po-
wi(cid:241)zany z dyrektyw(cid:241) ng-controller. Oznacza to, (cid:276)e ca(cid:228)y znacznik ul
znajduje si(cid:246) w zasi(cid:246)gu kontrolera IngredientsCtrl. Móg(cid:228)by(cid:264) w tym
miejscu zapyta(cid:232): co z rzeczywistym kontrolerem EditCtrl powi(cid:241)zanym
z szablonem? Jak si(cid:246) okazuje, IngredientsCtrl jest tworzony jako kon-
troler potomny EditCtrl i tym samym dziedziczy po nim. Dlatego te(cid:276)
dost(cid:246)p do obiektu recipe nast(cid:246)puje z poziomu kontrolera EditCtrl.
Ponadto kontroler IngredientsCtrl dodaje metod(cid:246) addIngredient() u(cid:276)y-
wan(cid:241) przez dyrektyw(cid:246) ng-click i dost(cid:246)pn(cid:241) jedynie w zasi(cid:246)gu znacz-
nika ul . Dlaczego zdecydowali(cid:264)my si(cid:246) na takie rozwi(cid:241)zanie? To naj-
lepszy sposób na rozdzielenie obowi(cid:241)zków. Po co umieszcza(cid:232) metod(cid:246)
addIngredient() w kontrolerze EditCtrl, skoro 99 szablonu jej nie po-
trzebuje? Kontrolery potomne i zagnie(cid:276)d(cid:276)one doskonale sprawdzaj(cid:241) si(cid:246)
w tego rodzaju sytuacjach i pozwalaj(cid:241) na oddzielenie logiki biznesowej
przez umieszczenie jej w (cid:228)atwiejszych do zarz(cid:241)dzania elementach.
120
(cid:95)
Rozdzia(cid:293) 4. Analiza aplikacji AngularJS
Kup książkęPoleć książkę(cid:120) Pozosta(cid:228)e dyrektywy, które chcemy tutaj omówi(cid:232), s(cid:241) kontrolkami prze-
znaczonymi do weryfikacji formularza sieciowego. We frameworku
AngularJS mo(cid:276)na bardzo (cid:228)atwo okre(cid:264)li(cid:232), (cid:276)e dane pole formularza jest
wymagane. W tym celu wystarczy doda(cid:232) do tego pola dyrektyw(cid:246) required
(jak to zrobiono w omawianym fragmencie kodu). Rodzi si(cid:246) jednak
pytanie: co dalej?
Przechodzimy do przycisku Zapisz. Zwró(cid:232) uwag(cid:246) na u(cid:276)ycie dyrekty-
wy ng-disabled, która ma warto(cid:264)(cid:232) recipeForm.$invalid. Cz(cid:228)on pierwszy
(recipeForm) to nazwa formularza zawieraj(cid:241)cego deklaracj(cid:246) dyrektywy.
Framework AngularJS dodaje do niego pewne zmienne specjalne (za-
liczamy do nich $valid i $invalid) pozwalaj(cid:241)ce na kontrolowanie ele-
mentów formularza sieciowego. AngularJS wyszukuje wszystkie wy-
magane elementy, a nast(cid:246)pnie odpowiednio uaktualnia wspomniane
zmienne specjalne. Je(cid:276)eli pole s(cid:228)u(cid:276)(cid:241)ce do podania nazwy przepisu kuli-
narnego pozostanie niewype(cid:228)nione, warto(cid:264)ci(cid:241) recipeForm.$invalid b(cid:246)dzie
true (a warto(cid:264)ci(cid:241) $valid b(cid:246)dzie false) i przycisk Zapisz zostanie zablo-
kowany.
Istnieje równie(cid:276) mo(cid:276)liwo(cid:264)(cid:232) okre(cid:264)lenia minimalnej i maksymalnej d(cid:228)ugo(cid:264)ci
pola tekstowego, a tak(cid:276)e wzorzec wyra(cid:276)enia regularnego przeznaczonego
do przeprowadzenia weryfikacji danego pola. Co wi(cid:246)cej, pewne funkcje
zaawansowane mo(cid:276)na wykorzysta(cid:232) do wy(cid:264)wietlania komunikatów b(cid:228)(cid:246)dów
po wyst(cid:241)pieniu pewnych okre(cid:264)lonych warunków. Spójrzmy na prosty
przyk(cid:228)ad:
form name= myForm
Nazwa u(cid:285)ytkownika: input type= text
name= userName
ng-model= user.name
ng-minlength= 3
span class= error
ng-show= myForm.userName.$error.minlength Zbyt krótka! /span
/form
Za pomoc(cid:241) u(cid:276)ycia dyrektywy ng-minlength w powy(cid:276)szym fragmencie ko-
du zdefiniowano, (cid:276)e nazwa u(cid:276)ytkownika musi sk(cid:228)ada(cid:232) si(cid:246) z przynajmniej
trzech znaków. Teraz formularz zostaje wype(cid:228)niony danymi pochodz(cid:241)cymi
z obiektu scope — w omawianym przyk(cid:228)adzie to jedynie userName. Wszystkie
pola tekstowe maj(cid:241) obiekt $error (zawiera informacje o rodzaju ewentual-
nego b(cid:228)(cid:246)du: required, minlength, maxlength lub pattern) oraz w(cid:228)a(cid:264)ciwo(cid:264)(cid:232)
$valid wskazuj(cid:241)c(cid:241) poprawno(cid:264)(cid:232) b(cid:241)d(cid:274) te(cid:276) niepoprawno(cid:264)(cid:232) danych wej(cid:264)ciowych.
Kontrolery, dyrektywy i us(cid:293)ugi
(cid:95)
121
Kup książkęPoleć książkęTakie rozwi(cid:241)zanie pozwala na selektywne wy(cid:264)wietlanie u(cid:276)ytkownikowi
komunikatu b(cid:228)(cid:246)du w zale(cid:276)no(cid:264)ci od jego rodzaju, jak to pokazano w powy(cid:276)-
szym fragmencie kodu.
Do drugiego przycisku do(cid:228)(cid:241)czona jest dyrektywa ng-click u(cid:276)ywana pod-
czas usuwania przepisu kulinarnego. Zwró(cid:232) uwag(cid:246), (cid:276)e przycisk jest wy-
(cid:264)wietlany tylko wtedy, gdy przepis nie zosta(cid:228) jeszcze zapisany. Wprawdzie
znacznie sensowniejsze wydaje si(cid:246) u(cid:276)ycie ng-hide= recipe.id , ale czasami
bardziej semantyczne rozwi(cid:241)zanie to ng-show= !recipe.id . Oznacza to wy-
(cid:264)wietlenie przycisku, gdy przepis kulinarny nie zawiera identyfikatora,
zamiast ukrywania przycisku, je(cid:264)li przepis ma zdefiniowany identyfikator.
Testy
Wstrzymywali(cid:264)my si(cid:246) z przedstawieniem testów wraz z kontrolerami, ale
musia(cid:228)e(cid:264) si(cid:246) spodziewa(cid:232), (cid:276)e kiedy(cid:264) wreszcie do nich przejdziemy. W tym
podrozdziale zaprezentowane zostan(cid:241) testy, które nale(cid:276)y utworzy(cid:232) dla
przygotowanego dot(cid:241)d fragmentu kodu. Dowiesz si(cid:246) równie(cid:276), jak tworzy
si(cid:246) takie testy.
Testy jednostkowe
Najwa(cid:276)niejszy rodzaj testów to testy jednostkowe. Pozwalaj(cid:241) one na spraw-
dzenie, czy opracowane kontrolery (dyrektywy i us(cid:228)ugi) maj(cid:241) prawid(cid:228)ow(cid:241)
struktur(cid:246) i konstrukcj(cid:246) oraz czy dzia(cid:228)aj(cid:241) zgodnie z oczekiwaniami.
Zanim przejdziemy do poszczególnych testów jednostkowych, warto spoj-
rze(cid:232) na szkielet przeznaczony dla wszystkich testów jednostkowych doty-
cz(cid:241)cych kontrolera:
describe( Kontrolery , function() {
var $scope, ctrl;
// W te(cid:286)cie nale(cid:298)y wskaza(cid:252) modu(cid:225).
beforeEach(module( guthub ));
beforeEach(function() {
this.addMatchers({
toEqualData: function(expected) {
return angular.equals(this.actual, expected);
}
});
});
describe( ListCtrl , function() {....});
// Miejsce na opisanie pozosta(cid:225)ych kontrolerów.
});
122
(cid:95)
Rozdzia(cid:293) 4. Analiza aplikacji AngularJS
Kup książkęPoleć książkęPrzygotowany szkielet (tutaj nadal wykorzystujemy styl Jasmine do tworze-
nia testów) wykonuje kilka zada(cid:254).
1. Tworzy globalnie (przynajmniej dla testu) dost(cid:246)pny obiekt scope i kon-
troler, a wi(cid:246)c nie trzeba si(cid:246) przejmowa(cid:232) tworzeniem nowej zmiennej
dla ka(cid:276)dego kontrolera.
2. Inicjalizuje modu(cid:228) u(cid:276)ywany przez aplikacj(cid:246) (w omawianym przyk(cid:228)adzie
jest to GutHub).
3. Dodaje specjalne dopasowanie nazywane equalData. Pozwala ono na prze-
prowadzanie asercji na obiektach (cid:274)ród(cid:228)a (na przyk(cid:228)ad przepisach kuli-
narnych) zwracanych przez us(cid:228)ug(cid:246) $resource lub na wywo(cid:228)anie RESTful.
Pami(cid:246)taj o konieczno(cid:264)ci dodania specjalnego dopasowania na-
zywanego equalData za ka(cid:276)dym razem, gdy zachodzi potrzeba
stosowania asercji na zwróconych obiektach ngResource. Wi(cid:241)(cid:276)e
si(cid:246) to z faktem, (cid:276)e zwrócone obiekty ngResource maj(cid:241) metody do-
datkowe, których zwyk(cid:228)e wykonanie zako(cid:254)czy si(cid:246) niepowodze-
niem, poniewa(cid:276) oczekiwane s(cid:241) wywo(cid:228)ania equalData.
Maj(cid:241)c przygotowany szkielet, spójrzmy na gotowy test jednostkowy prze-
znaczony dla kontrolera ListCtrl:
describe( ListCtrl , function() {
var mockBackend, recipe;
// _$httpBackend_ to nazwa taka sama jak $httpBackend. Zastosowany zapis s(cid:225)u(cid:298)y do odró(cid:298)nienia
// zmiennych wstrzykni(cid:266)tych od zmiennych lokalnych.
beforeEach(inject(function($rootScope, $controller, _$httpBackend_, Recipe) {
recipe = Recipe;
mockBackend = _$httpBackend_;
$scope = $rootScope.$new();
ctrl = $controller( ListCtrl , {
$scope: $scope,
recipes: [1, 2, 3]
});
}));
it( Wynikiem powinna by(cid:202) lista przepisów kulinarnych , function() {
expect($scope.recipes).toEqual([1, 2, 3]);
});
});
Jak zapewne pami(cid:246)tasz, kontroler ListCtrl nale(cid:276)y do najprostszych w apli-
kacji. Konstruktor kontrolera pobiera po prostu list(cid:246) przepisów, a nast(cid:246)p-
nie zapisuje je w obiekcie. Wprawdzie mo(cid:276)na do tego utworzy(cid:232) test, ale
wydaje si(cid:246) to zb(cid:246)dne. W omawianym przyk(cid:228)adzie mimo wszystko utwo-
rzyli(cid:264)my test, poniewa(cid:276) testy jednostkowe s(cid:241) wspania(cid:228)e!
Testy
(cid:95)
123
Kup książkęPoleć książkęZnacznie ciekawiej robi si(cid:246) w przypadku us(cid:228)ugi MultiRecipeLoader. Wy-
mieniona us(cid:228)uga jest odpowiedzialna za pobranie listy przepisów kulinar-
nych z serwera i przekazanie ich jako argumentu (kiedy zastosowana jest
prawid(cid:228)owa konfiguracja za pomoc(cid:241) us(cid:228)ugi $route):
describe( MultiRecipeLoader , function() {
var mockBackend, recipe, loader;
// _$httpBackend_ to nazwa taka sama jak $httpBackend. Zastosowany zapis s(cid:225)u(cid:298)y do odró(cid:298)nienia
// zmiennych wstrzykni(cid:266)tych od zmiennych lokalnych.
beforeEach(inject(function(_$httpBackend_, Recipe, MultiRecipeLoader) {
recipe = Recipe;
mockBackend = _$httpBackend_;
loader = MultiRecipeLoader;
}));
it( Wynikiem powinno by(cid:202) wczytanie listy przepisów kulinarnych , function() {
mockBackend.expectGET( /recipes ).respond([{id: 1}, {id: 2}]);
var recipes;
var promise = loader();
promise.then(function(rec) {
recipes = rec;
});
expect(recipes).toBeUndefined();
mockBackend.flush();
expect(recipes).toEqualData([{id: 1}, {id: 2}]);
});
});
// Miejsce na opisanie pozosta(cid:225)ych kontrolerów.
Test us(cid:228)ugi MultiRecipeLoader odbywa si(cid:246) przez przygotowanie us(cid:228)ugi Http
(cid:180)Backend w naszym te(cid:264)cie. Obiekt pochodzi z pliku angular-mocks.js i jest
do(cid:228)(cid:241)czany w trakcie przeprowadzania testów. Po prostu wstrzykni(cid:246)cie go
do metody beforeEach() jest wystarczaj(cid:241)ce, aby mo(cid:276)na by(cid:228)o konfigurowa(cid:232)
oczekiwania. W drugim, znacznie ciekawszym te(cid:264)cie oczekiwanie zosta(cid:228)o
zdefiniowane jako wywo(cid:228)anie server GET do recipes, a wynikiem powinna
by(cid:232) tablica obiektów. Nast(cid:246)pnie u(cid:276)ywamy dopasowania w celu spraw-
dzenia, czy uzyskany wynik jest dok(cid:228)adnie zgodny z oczekiwaniami. Zwró(cid:232)
uwag(cid:246) na wywo(cid:228)anie flush() w obiekcie makiety, przekazuj(cid:241)ce odpowied(
Pobierz darmowy fragment (pdf)