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.
3
Spis treļci
Wprowadzenie ...........................................................................................7
Konwencje zastosowane w ksiñĔce 8
UĔycie przykäadowych kodów
8
Podziökowania 9
Rozdziaĥ 1. Wprowadzenie do AngularJS ................................................11
Koncepcje 12
Przykäad — koszyk na zakupy
18
Co dalej?
21
Rozdziaĥ 2. Anatomia aplikacji AngularJS ...............................................23
Wywoäanie AngularJS
23
Architektura MVC
24
Szablony i doäñczanie danych
27
Organizacja zaleĔnoĈci za pomocñ moduäów 51
Formatowanie danych za pomocñ filtrów
55
Zmiana widoków za pomocñ tras i usäugi $location
57
Komunikacja z serwerem
61
UĔycie dyrektyw do zmiany elementów drzewa DOM
63
Weryfikacja danych wejĈciowych uĔytkownika 65
Co dalej?
67
Rozdziaĥ 3. Programowanie w AngularJS ...............................................69
Organizacja projektu
70
Narzödzia 73
Uruchamianie aplikacji
75
Testowanie w AngularJS
76
Testy jednostkowe
79
4
_ Spis
treļci
Testy typu E2E/integracji
80
Kompilacja 82
Inne wspaniaäe narzödzia 84
Narzödzie Yeoman — optymalizacja sposobu pracy
88
Integracja AngularJS i RequireJS
92
Rozdziaĥ 4. Analiza aplikacji AngularJS .................................................101
Aplikacja 101
Relacje miödzy modelem, kontrolerem i szablonem
102
Kontrolery, dyrektywy i usäugi 105
Testy 122
Rozdziaĥ 5. Komunikacja z serwerami .................................................. 129
Komunikacja za pomocñ usäugi $http
129
Testy jednostkowe
135
Praca z zasobami RESTful
137
Usäuga $q i obietnica
143
Przechwycenie odpowiedzi
145
Kwestie bezpieczeþstwa 146
XSRF 147
Rozdziaĥ 6. Dyrektywy ........................................................................... 149
Dyrektywy i weryfikacja kodu HTML
149
Ogólny opis API
150
Co dalej?
170
Rozdziaĥ 7. Inne kwestie .........................................................................171
Usäuga $location
171
Metody moduäu AngularJS
178
Komunikacja miödzy zasiögami
za pomocñ $on, $emit i $broadcast
182
Ciasteczka 184
Internacjonalizacja i lokalizacja
185
Oczyszczanie kodu HTML i moduä Sanitize
188
Spis
treļci
_
5
Rozdziaĥ 8. Ļciéga i podpowiedzi ...........................................................191
Opakowanie kontrolki jQuery datepicker
191
Lista klubów sportowych — filtrowanie i komunikacja
196
Przekazywanie plików w aplikacji AngularJS
201
UĔycie biblioteki Socket.IO
204
Prosta usäuga stronicowania
207
Praca z serwerami i logowaniem
210
Podsumowanie 214
Skorowidz ............................................................................................... 216
101
ROZDZIAĤ 4.
Analiza aplikacji AngularJS
W rozdziale 2. przedstawiono pewne najczöĈciej uĔywane funkcje framewor-
ka AngularJS, natomiast w rozdziale 3. zajöliĈmy siö zagadnieniami zwiñ-
zanymi ze sposobem prowadzenia prac programistycznych. Zamiast konty-
nuowaè wñtek i podobnie szczegóäowo omawiaè poszczególne funkcje,
w tym rozdziale przejdziemy do maäej, rzeczywistej aplikacji. Na jej pod-
stawie dowiesz siö, jak poäñczyè ze sobñ omówione dotñd fragmenty caäoĈci
i utworzyè rzeczywistñ, dziaäajñcñ aplikacjö.
Zamiast od razu przedstawiaè caäñ aplikacjö, bödziemy jñ poznawaè w ma-
äych czöĈciach, omawiaè interesujñce zagadnienia zwiñzane z danym frag-
mentem i tym samym powoli budowaè kompletnñ aplikacjö, która bödzie
gotowa, zanim ukoþczysz lekturö rozdziaäu.
Aplikacja
GutHub to prosta aplikacja przeznaczona do zarzñdzania przepisami kuli-
narnymi. Zostaäa zaprojektowana w taki sposób, aby przechowywaè przepi-
sy kulinarne i jednoczeĈnie pokazywaè róĔne interesujñce aspekty aplikacji
AngularJS. Oto cechy charakteryzujñce tworzonñ przez nas aplikacjö:
x
ma ukäad skäadajñcy siö z dwóch kolumn;
x
pasek nawigacyjny znajduje siö po lewej stronie;
x
pozwala na dodawanie nowych przepisów kulinarnych;
x
umoĔliwia przeglñdanie istniejñcych przepisów kulinarnych.
Widok gäówny aplikacji znajduje siö po prawej stronie. W zaleĔnoĈci od
adresu URL ulega ona zmianie i wyĈwietla listö przepisów kulinarnych,
102 _
Rozdziaĥ 4. Analiza aplikacji AngularJS
szczegóäy dotyczñce konkretnego przepisu lub edytowalny formularz po-
zwalajñcy na dodanie nowego bñdĒ na edycjö istniejñcego. Uruchomionñ
aplikacjö pokazano na rysunku 4.1.
Rysunek 4.1. GutHub, czyli prosta aplikacja przeznaczona do zarzñdzania przepisami
kulinarnymi
Caäa aplikacja jest dostöpna w repozytorium GitHub na stronie: https://github.
com/shyamseshadri/angularjs-book/tree/master/chapter4
.
Relacje miýdzy modelem,
kontrolerem i szablonem
Zanim przejdziemy do omawiania aplikacji, zatrzymajmy siö na chwilö
i zastanówmy, jak trzy fragmenty aplikacji wspóädziaäajñ ze sobñ oraz jak
powinniĈmy je traktowaè.
Model
jest istotñ aplikacji. Powtórz to zdanie kilkakrotnie. Dziaäanie caäej
aplikacji opiera siö na modelu, od którego zaleĔñ: wyĈwietlany widok, dane
wyĈwietlane przez widok, zapisywane informacje i dosäownie wszystko.
Warto wiöc poĈwiöciè nieco czasu i dokäadnie przemyĈleè model — jakie
wäaĈciwoĈci powinien mieè obiekt modelu, jak bödzie pobierany z serwera,
jak bödzie zapisywany i tak dalej. Ze wzglödu na to, Ĕe uaktualnienie wido-
ku nastöpuje automatycznie dziöki uĔyciu wiñzania danych, uwagö naleĔy
skoncentrowaè na modelu.
Relacje miýdzy modelem, kontrolerem i szablonem
_ 103
Kontroler
zawiera logikö biznesowñ i okreĈla miödzy innymi: jak bödzie po-
bierany model, jakie bödñ rodzaje operacji przeprowadzanych na modelu, ja-
kich informacji widok potrzebuje z modelu, a takĔe jak przeksztaäciè model,
aby uzyskaè potrzebne dane. Ponadto przeprowadzanie weryfikacji, wy-
konywanie wywoäaþ do serwera, umieszczanie odpowiednich danych w wi-
doku oraz wäaĈciwie wszystko inne powiñzane z wymienionymi dziaäania-
mi to równieĔ aktywnoĈè definiowana w kontrolerze.
I na koniec szablon okreĈla sposób prezentacji modelu oraz interakcji uĔyt-
kownika z aplikacjñ. Zadania wykonywane przez szablon powinny byè ogra-
niczone do wymienionych poniĔej:
x
wyĈwietlanie modelu;
x
definiowanie sposobów, na jakie uĔytkownik moĔe korzystaè z aplikacji
— klikniöcia, pola danych wejĈciowych i tak dalej;
x
nadawanie stylu aplikacji oraz okreĈlanie, jak i kiedy pewne elementy
majñ byè wyĈwietlane — pokazywanie lub ukrywanie i tak dalej;
x
filtrowanie i formatowanie danych (zarówno wejĈciowych, jak i wyj-
Ĉciowych).
Trzeba pamiötaè, Ĕe szablon w AngularJS niekoniecznie jest widokiem
w architekturze MVC (model – widok – kontroler). Zamiast tego widok jest
skompilowanñ wersjñ wykonywanego szablonu, rodzajem poäñczenia sza-
blonu i modelu.
W szablonie nie naleĔy umieszczaè Ĕadnego rodzaju logiki biznesowej
ani definiowaè zachowania — tego rodzaju dane powinny znajdowaè siö
w kontrolerze. Zachowanie prostoty szablonów pozwala na wäaĈciwñ sepa-
racjö obowiñzków, a ponadto na przetestowanie wiökszoĈci kodu za po-
mocñ jedynie testów jednostkowych. Szablony powinny byè testowane za
pomocñ testów scenariuszy.
W tym miejscu mógäbyĈ zapytaè: gdzie naleĔy umieszczaè polecenia od-
powiedzialne za modyfikacje obiektowego modelu dokumentu? Operacje
na elementach drzewa DOM nie powinny byè definiowane w kontrolerach
lub szablonach. Najlepszym miejscem dla nich sñ dyrektywy AngularJS,
choè czasami wspomniane operacje mogñ byè stosowane za pomocñ usäug,
co pozwala na unikniöcie powielania kodu. Przykäad takiego rozwiñzania
w aplikacji GutHub równieĔ zostanie zaprezentowany i omówiony.
Bez zbödnych ceregieli przechodzimy wiöc do modelu.
104 _
Rozdziaĥ 4. Analiza aplikacji AngularJS
Model
W omawianej aplikacji staramy siö zachowaè maksymalnñ prostotö modelu
— bödñ to po prostu przepisy kulinarne. Wspomniane przepisy to jedyny
obiekt modelu w caäej aplikacji. Wszystkie pozostaäe komponenty zostanñ
zbudowane wokóä modelu.
KaĔdy przepis skäada siö z nastöpujñcych wäaĈciwoĈci:
x
identyfikator, jeĈli przepis zostaä zapisany na serwerze,
x
nazwa,
x
krótki opis,
x
sposób przygotowania,
x
informacje o ewentualnym wyróĔnieniu przepisu,
x
tablica skäadników podanych w postaci nazwy, iloĈci i jednostki miary.
I to tyle, model jest niezwykle prosty. Pozostaäe komponenty aplikacji utwo-
rzymy na podstawie wymienionego modelu. PoniĔej przedstawiono przy-
käadowy przepis kulinarny w formacie JSON (przepis ten zostaä pokazany
na rysunku 4.1 we wczeĈniejszej czöĈci rozdziaäu):
{
"id": "1",
"title": "Ciasteczka",
"description": "Wyborne, chrupiÈce na zewnÈtrz, ciÈgliwe" +
" w Ărodku i ociekajÈce pysznÈ czekoladÈ " +
"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Ú w ciepïych ciasteczkach\n" +
"4. Z innego ěródïa naucz siÚ, jak wypiekaÊ wyborne ciasteczka"
}
W celu zachowania prostoty przykäadu nie bödziemy zajmowaè siö serwe-
rem, z którego przepisy kulinarne sñ pobierane lub w którym sñ zapisy-
wane. Kod serwera znajduje siö w repozytorium w serwisie GitHub, a do
jego uruchomienia säuĔy polecenie
node web-server.js
, które trzeba wydaè
z poziomu podstawowego katalogu aplikacji GutHub. Przechodzimy teraz
do znacznie bardziej skomplikowanych funkcji interfejsu uĔytkownika, jakie
moĔna utworzyè na postawie naszego prostego modelu.
Kontrolery, dyrektywy i usĥugi
_ 105
Kontrolery, dyrektywy i usĥugi
Wreszcie moĔemy zajñè siö ciekawszymi aspektami tworzonej przez nas
aplikacji. W pierwszej kolejnoĈci spojrzymy na kod dyrektyw i usäug i po-
wiemy sobie nieco o sposobie jego dziaäania. Nastöpnie przejdziemy do
wielu kontrolerów niezbödnych do zapewnienia prawidäowego dziaäania
tworzonej aplikacji.
Usĥugi
PoniĔej przedstawiono kod Ēródäowy usä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ĝna pobraÊ 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ĝna pobraÊ przepisu ' + $route.current.params.recipeId);
});
return delay.promise;
};
}]);
Najpierw zajmiemy siö usäugami. Nie jest to nasze pierwsze spotkanie
z usäugami — zetknöliĈmy siö z nimi juĔ w rozdziale 2. Tutaj zostanñ omó-
wione nieco dokäadniej.
W przedstawionym pliku znajdujñ siö trzy usäugi AngularJS.
106 _
Rozdziaĥ 4. Analiza aplikacji AngularJS
Istnieje usäuga przepisu kulinarnego, która zwraca tak zwany AngularJS
Resource. Jest to zasób RESTful prowadzñcy do serwera RESTful. Wspo-
mniany zasób hermetyzuje dziaäajñcñ na niskim poziomie usäugö
$http
, a tym
samym programista musi utworzyè jedynie kod odpowiedzialny za pracö
z obiektami.
Za pomocñ tylko pojedynczego wiersza kodu (
return $resource
) oraz oczywi-
Ĉcie zaleĔnoĈci w module
guthub.services
obiekt
Recipe
moĔe byè uĔyty jako
argument w dowolnym kontrolerze — zostanie wówczas wstrzykniöty do
wskazanego kontrolera. Co wiöcej, kaĔdy obiekt
Recipe
ma wbudowane
wymienione poniĔej metody:
x
Recipe.get()
,
x
Recipe.save()
,
x
Recipe.query()
,
x
Recipe.remove()
,
x
Recipe.delete()
.
JeĔeli zamierzasz uĔyè metody Recipe.delete() i chcesz zapewniè
dziaäanie aplikacji w przeglñdarce Internet Explorer, wtedy mu-
sisz uĔyè wywoäania w postaci Recipe['delete'](). Wynika to
z faktu, Ĕe delete jest säowem kluczowym w przeglñdarce In-
ternet Explorer.
Z wymienionych powyĔej metod wszystkie poza
query()
dziaäajñ z poje-
dynczym przepisem kulinarnym. Natomiast wartoĈciñ zwrotnñ metody
query()
jest domyĈlnie tablica przepisów kulinarnych.
Wiersz kodu deklarujñcy zasób (
return $resource
) wykonuje równieĔ kilka
innych uĔytecznych zadaþ.
1.
Zwróè uwagö na
:id
w adresie URL wskazanym dla zasobu RESTful.
Wspomniany identyfikator oznacza, Ĕe w trakcie wykonywania dowol-
nego zapytania (na przykäad za pomocñ
Recipe.get()
), jeĈli przekaĔesz
obiekt wraz z wäaĈciwoĈciñ
id
, wówczas jej wartoĈè zostanie umiesz-
czona na koþcu adresu URL.
Oznacza to, Ĕe wywoäanie
Recipe.get({id: 15})
faktycznie bödzie wy-
woäaniem do /recipe/15.
2.
MógäbyĈ zapytaè w tym miejscu: co z drugim obiektem, na przykäad
{id:
@id}
? Wiersz kodu jest wart tysiñca säów objaĈnienia, wiöc przejdĒmy
od razu do odpowiedniego przykäadu.
Kontrolery, dyrektywy i usĥugi
_ 107
Przyjmujemy zaäoĔenie, Ĕe dostöpny jest obiekt
Recipe
zawierajñcy
wszystkie niezbödne informacje, miödzy innymi wartoĈè
id
.
Wspomniany obiekt moĔna zapisaè za pomocñ poniĔszego fragmentu
kodu:
// PrzyjĊto zaáoĪenie, Īe obiekt existingRecipeObj ma wszystkie niezbĊdne wáaĞciwoĞci,
// w tym id (na przykáad o wartoĞci 13).
var recipe = new Recipe(existingRecipeObj);
recipe.$save();
Przedstawiony kod spowoduje wykonanie Ĕñdania
POST
do /recipe/13.
Fragment
@id
powoduje pobranie wartoĈci wäaĈciwoĈci
id
obiektu i uĔy-
cie jej jako parametru
id
. Takie rozwiñzanie przyjöto dla wygody — po-
zwala ono zaoszczödziè kilka wierszy kodu.
W pliku apps/scripts/services/services.js istniejñ jeszcze dwie inne usäugi.
Obie zaliczajñ siö do komponentów wczytujñcych: pierwsza (
RecipeLoader
)
wczytuje pojedynczy przepis, natomiast druga (
MultiRecipeLoader
) jest prze-
znaczona do wczytywania wszystkich przepisów kulinarnych. Wymienio-
ne usäugi sñ uĔywane podczas konfiguracji tras, a sposób dziaäania tych
usäug jest bardzo podobny i zostaä przedstawiony poniĔej.
1.
Utworzenie obiektu wstrzymanego
$q
(jest to rodzaj obietnicy frameworka
AngularJS stosowanej w celu äñczenia funkcji asynchronicznych).
2.
Wykonanie wywoäania do serwera.
3.
OkreĈlenie obiektu wstrzymanego, gdy serwer zwraca wartoĈè.
4.
Zwrot obietnicy, która bödzie uĔywana przez mechanizm routingu fra-
meworka AngularJS.
Obietnice frameworka AngularJS
Obietnica to interfejs przeznaczony do pracy z obiektami, które sñ zwra-
cane lub bödñ wypeänione w przyszäoĈci (w zasadzie sñ to akcje asyn-
chroniczne). Ogólnie rzecz biorñc, na obietnicö skäada siö obiekt oraz
funkcja
then()
.
Aby zobaczyè zalety obietnic, spójrzmy na przykäad, w którym konieczne
jest pobranie profilu uĔytkownika:
var currentProfile = null;
var username = 'dowolnaNazwa';
fetchServerConfig(function(serverConfig) {
fetchUserProfiles(serverConfig.USER_PROFILES, username,
function(profiles) {
currentProfile = profiles.currentProfile;
});
});
108 _
Rozdziaĥ 4. Analiza aplikacji AngularJS
Z powyĔszym podejĈciem zwiñzanych jest kilka problemów.
1.
Kod wynikowy bödzie koszmarnie powcinany, zwäaszcza jeĈli zajdzie
koniecznoĈè poäñczenia kilku wywoäaþ.
2.
Bäödy zgäaszane miödzy wywoäaniami zwrotnymi i funkcjami majñ
tendencjö do znikania, jeĔeli nie zostanñ röcznie obsäuĔone na kaĔdym
etapie.
3.
W wewnötrznym wywoäaniu zwrotnym konieczna jest hermetyza-
cja logiki zwiñzanej z dziaäaniami przeprowadzanymi za pomocñ
zmiennej currentProfile bezpoĈrednio lub za pomocñ oddzielnej
funkcji.
Obietnica rozwiñzuje wymienione problemy. Zanim siö przekonasz, w jaki
sposób, najpierw spójrz na ten sam kod, ale zaimplementowany z uĔyciem
obietnic:
var currentProfile = fetchServerConfig().then(function(serverConfig) {
return fetchUserProfiles(serverConfig.USER_PROFILES, username);
}).then(function(profiles) {
return profiles.currentProfile;
}, function(error) {
// Obsáuga báĊdów powstaáych w fetchServerConfig()
// lub w fetchUserProfiles().
});
Zwróè uwagö na zalety nowego rozwiñzania.
1.
Istnieje moĔliwoĈè äñczenia wywoäaþ funkcji i nie spowoduje to
koszmaru zwiñzanego ze stosowaniem wciöè w kodzie.
2.
Masz gwarancjö, Ĕe wywoäanie poprzedniej funkcji zostanie zakoþ-
czone, zanim nastñpi wywoäanie kolejnej funkcji w äaþcuchu.
3.
KaĔde wywoäanie then() pobiera dwa argumenty (oba to funkcje).
Pierwszy to funkcja wywoäywana w przypadku sukcesu operacji,
natomiast drugi to procedura obsäugi bäödów.
4.
W przypadku wystñpienia bäödów w äaþcuchu wspomniany bäñd bö-
dzie propagowany przez pozostaäe procedury obsäugi bäödów. Dla-
tego teĔ bäñd w dowolnym wywoäaniu zwrotnym moĔna obsäuĔyè
na koþcu.
MógäbyĈ zapytaè: co z wywoäaniami
resolve()
i
reject()
? Wywoäanie
deferred()
to we frameworku AngularJS sposób tworzenia obietnic. Z kolei
wywoäanie
resolve()
powoduje speänienie obietnicy (i wywoäanie pro-
cedury obsäugi w przypadku sukcesu operacji), podczas gdy wywoäanie
reject
powoduje wywoäanie procedury obsäugi bäödów w obietnicy.
Kontrolery, dyrektywy i usĥugi
_ 109
Do tego zagadnienia powrócimy jeszcze podczas konfiguracji tras.
Dyrektywy
Przechodzimy teraz do dwóch dyrektyw, które bödñ uĔywane w tworzo-
nej tutaj aplikacji.
butterbar
Ta dyrektywa bödzie pokazana lub ukryta w trakcie wczytywania in-
formacji przez stronö, a takĔe po zmianie trasy. Jest poäñczona z me-
chanizmem zmiany trasy i automatycznie zostaje ukryta lub umiesz-
czona w znaczniku na podstawie stanu strony.
focus
Ta dyrektywa jest uĔywana w celu zagwarantowania, Ĕe pewne pola
tekstowe (lub elementy) formularza sieciowego sñ aktywne.
Spójrz na przykä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ñ wäaĈciwoĈciñ
link
. Dokäadne omówienie tematu tworzenia wäasnych dyrektyw znajdziesz
w rozdziale 6., teraz musisz jedynie wiedzieè o dwóch rzeczach.
110 _
Rozdziaĥ 4. Analiza aplikacji AngularJS
1.
Dyrektywy przechodzñ przez proces skäadajñcy siö z dwóch etapów.
Na pierwszym etapie (faza kompilacji) nastöpuje wyszukanie wszystkich
dyrektyw doäñczonych do elementu drzewa DOM, a nastöpnie ich
przetworzenie. Wszelkie operacje na elementach drzewa DOM sñ
przeprowadzane na etapie kompilacji. Na koþcu fazy otrzymujesz funk-
cjö äñczñcñ.
2.
Na drugim etapie (faza äñczenia — tö fazö wczeĈniej wykorzystaliĈmy)
wygenerowany szablon elementów drzewa DOM jest doäñczany do za-
siögu (
scope
). Ponadto dodawane sñ wszelkie komponenty monitorujñce
lub nasäuchujñce, co oznacza powstanie funkcjonujñcego wiñzania miö-
dzy zasiögiem
scope
i elementem. Wszystko, co jest powiñzane z zasiögiem
scope
, zachodzi na etapie äñczenia.
Co siö dzieje w naszej dyrektywie? Zajrzyjmy do niej i przekonajmy siö.
Dyrektywa
butterbar
moĔe byè uĔywana w nastöpujñcy sposób:
<div butterbar>Komunikat informujÈcy o wczytywaniu...</div>
Dziaäanie dyrektywy polega na ukryciu elementu oraz dodaniu dwóch
komponentów monitorujñcych zasiög gäówny (
scope
). Za kaĔdym razem,
gdy rozpoczyna siö zmiana trasy, nastöpuje pokazanie elementu (przez
zmianö jego klasy), a po zakoþczonej powodzeniem zmianie trasy mamy
ponowne ukrycie dyrektywy
butterbar
.
Interesujñcñ cechñ, na którñ warto tutaj zwróciè uwagö, jest sposób wstrzyk-
niöcia
$rootScope
do dyrektywy. Wszystkie dyrektywy majñ bezpoĈrednie
powiñzanie z systemem wstrzykiwania zaleĔnoĈci w AngularJS, co pozwala
na wstrzykiwanie do nich usäug oraz innych niezbödnych komponentów.
Ostatnia kwestia warta uwagi to API przeznaczone do pracy z elementem.
ProgramiĈci przyzwyczajeni do biblioteki jQuery bödñ szczöĈliwi, wiedzñc,
Ĕe zastosowanie ma doskonale znana im skäadnia (
addClass
,
removeClass
).
Framework AngularJS implementuje pewien podzbiór wywoäaþ jQuery,
a wiöc biblioteka jQuery stanowi opcjonalnñ zaleĔnoĈè dla kaĔdego projektu
AngularJS. JeĔeli w projekcie chcesz wykorzystaè peäniö moĔliwoĈci ofe-
rowanych przez jQuery, wtedy powinieneĈ wiedzieè, Ĕe AngularJS uĔywa
jej zamiast wbudowanej implementacji.
Druga dyrektywa (
focus
) jest znacznie prostsza. Jej dziaäanie polega na wywo-
äaniu metody
focus()
dla bieĔñcego elementu. MoĔna jñ wywoäaè przez doda-
nie atrybutu
focus
do dowolnego elementu danych wejĈciowych, na przykäad:
<input type="text" focus></input>
Podczas wczytywania strony element automatycznie jest aktywny.
Kontrolery, dyrektywy i usĥugi
_
111
Kontrolery
Po zaprezentowaniu dyrektyw i usäug moĔesz wreszcie przejĈè do kon-
trolerów, których w naszej aplikacji mamy piöè. Wszystkie zostaäy zdefi-
niowane w pojedynczym pliku (app/scripts/controllers/controllers.js), ale omó-
wimy je tutaj pojedynczo. Przechodzimy wiöc do pierwszego kontrolera
(
ListCtrl
), odpowiedzialnego za wyĈwietlenie listy wszystkich przepisów
kulinarnych przechowywanych w systemie.
app.controller('ListCtrl', ['$scope', 'recipes', function($scope, recipes) {
$scope.recipes = recipes;
}]);
Zwróè uwagö na jednñ bardzo waĔnñ kwestiö w przypadku omawianego
kontrolera: w konstruktorze nie zawiera on Ĕadnego kodu dotyczñcego
nawiñzania poäñczenia z serwerem i pobrania przepisów kulinarnych.
Zamiast tego kod zajmuje siö obsäugñ wczeĈniej pobranych przepisów. Byè
moĔe zastanawiasz siö, jak to zostaäo zrobione. CóĔ, dokäadnñ odpowiedĒ
poznasz w sekcji poĈwiöconej routingowi, ale juĔ teraz moĔemy powie-
dzieè, Ĕe wiñĔe siö to z usäugñ
MultiRecipeLoader
. Po prostu o tym pamiötaj.
Po zapoznaniu siö z kontrolerem
ListCtrl
zobaczysz, Ĕe pozostaäe sñ caä-
kiem podobne do omówionego. Mimo wszystko zaprezentujemy je po
kolei, wskazujñc przy tym interesujñce aspekty:
app.controller('ViewCtrl', ['$scope', '$location', 'recipe',
function($scope, $location, recipe) {
$scope.recipe = recipe;
$scope.edit = function() {
$location.path('/edit/' + recipe.id);
};
}]);
Interesujñcym aspektem kontrolera
ViewCtrl
jest funkcja edycji udostöp-
niana obiektowi
scope
. Zamiast pokazywaè i ukrywaè pola lub stosowaè
podobne rozwiñzanie, kontroler wykorzystuje framework AngularJS i zleca
mu wykonanie najtrudniejszych zadaþ (powinieneĈ stosowaè takie samo
podejĈcie!). Funkcja
edit()
po prostu zmienia adres URL na odpowiednik
przepisu kulinarnego, a AngularJS zajmuje siö resztñ. Ponadto framework
wykrywa zmianö adresu URL i wczytuje odpowiedni widok (w trybie
edycji bö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;
112
_
Rozdziaĥ 4. Analiza aplikacji AngularJS
$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Ĉciñ sñ metody
save()
i
remove()
, które
EditCtrl
udostöpnia obiektowi
scope
.
Metoda
save()
obiektu
scope
dziaäa zgodnie z oczekiwaniami. Zapisuje bie-
Ĕñcy przepis kulinarny, a po zakoþczeniu operacji zapisu przekierowuje
uĔytkownika do widoku wyĈwietlajñcego ten sam przepis. Funkcja wywoäa-
nia zwrotnego jest uĔyteczna, poniewaĔ pozwala na przeprowadzenie pew-
nych operacji po zapisie.
Istniejñ dwa sposoby zapisania przepisu. Jeden z nich zostaä przedstawio-
ny w kodzie i polega na wywoäaniu funkcji
$scope.recipe.$save()
. Takie
rozwiñzanie jest moĔliwe tylko dlatego, Ĕe
recipe
jest obiektem zasobu zwró-
conego przez
RecipeLoader
.
Natomiast drugi sposób zapisu to wywoäanie:
Recipe.save(recipe);
Metoda
remove()
równieĔ naleĔy do prostych, a jej dziaäanie polega na
usuniöciu przepisu z obiektu
scope
oraz przekierowaniu uĔytkownika na
stronö gäównñ. Zwróè uwagö, Ĕe nie powoduje to rzeczywistego usuniöcia
przepisu kulinarnego z serwera. Wykonanie dodatkowego wywoäania nie
powinno byè zbyt trudne.
Kolejny kontroler nosi nazwö
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);
});
};
}]);
Kontrolery, dyrektywy i usĥugi
_
113
Ten kontroler jest niemal dokäadnie taki sam jak
EditCtrl
(jako èwiczenie
mógäbyĈ oba wymienione kontrolery poäñczyè w jeden). Jedyna róĔnica
polega na tym, Ĕe pierwszym krokiem w dziaäaniu kontrolera
NewCtrl
jest
utworzenie nowego przepisu kulinarnego (wspomniany przepis to zasób,
a wiöc kontroler ma funkcjö
save()
). Caäa pozostaäa funkcjonalnoĈè nie
ulega zmianie.
Ostatni kontroler to
IngredientsCtrl
. Jest to kontroler specjalny, ale zanim
przejdziemy do jego omówienia, spójrz na tworzñ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ñd kontrolery sñ poäñczone z okreĈlonymi wi-
dokami w interfejsie uĔytkownika. Pod tym wzglödem kontroler
Ingredient-
sCtrl
dziaäa nieco inaczej. To po prostu kontroler potomny uĔywany do
edycji stron i hermetyzacji pewnych funkcji niepotrzebnych na ogólnym
poziomie. Warto w tym miejscu wspomnieè o pewnej interesujñcej kwestii.
Skoro to kontroler potomny, dziedziczy obiekt
scope
po kontrolerze nad-
rzödnym (w omawianym przykäadzie jest to kontroler
EditCtrl
lub
NewCtrl
).
Dlatego teĔ uzyskanie dostöpu do obiektu
$scope.recipe
odbywa siö z po-
ziomu kontrolera nadrzödnego.
Sam kod kontrolera nie zawiera nic szczególnie interesujñcego lub unikalnego.
Dodaje kilka nowych skäadników do tablicy skäadników przepisu kulinarnego
lub teĔ usuwa okreĈlony skäadnik z listy.
W ten sposób omówiliĈmy wszystkie kontrolery. Jedyny fragment kodu
JavaScript, jaki pozostaä 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();
}
},
114 _
Rozdziaĥ 4. Analiza aplikacji AngularJS
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Ĉniejszñ obietnicñ docieramy do miejsca, w którym uĔy-
wana jest funkcja
resolve()
. W poprzednim fragmencie kodu skonfigu-
rowano moduä
guthub
AngularJS, a takĔe trasy i szablony wykorzystywane
w aplikacji.
Kod äñczy dyrektywy z utworzonymi przez nas usäugami, a nastöpnie
wskazuje róĔne trasy, które bödñ stosowane w aplikacji.
Dla kaĔdej trasy definiowany jest adres URL, kontroler odpowiedzialny za ob-
säugö danego adresu, wczytywany szablon, a takĔe (wreszcie) obiekt
resolve
.
Obiekt
resolve
nakazuje frameworkowi AngularJS speänienie wymagaþ
kaĔdego klucza, zanim trasa bödzie mogäa zostaè uĔyta do wyĈwietlenia
odpowiedniego widoku uĔytkownikowi. Zadanie aplikacji polega na wczy-
taniu wszystkich przepisów kulinarnych (lub tylko wskazanego), a serwer
ma udzieliè odpowiedzi przed wyĈwietleniem strony uĔytkownikowi. Do-
stawcö tras informujemy wiöc o posiadaniu przepisów kulinarnych (lub
przepisu), a nastöpnie podajemy mu sposób, w jaki majñ byè pobrane dane.
W trakcie wykonywania operacji pobierania danych wykorzystywane sñ
dwie usäugi (
MultiRecipeLoader
i
RecipeLoader
) zdefiniowane na poczñtku
tworzenia aplikacji. Framework AngularJS zostaä doĈè sprytnie zaprojek-
towany — jeĔeli wartoĈciñ zwrotnñ funkcji
resolve()
bödzie obietnica An-
gularJS, wtedy framework poczeka na speänienie wspomnianej obietnicy
przed przejĈciem dalej. Oznacza to koniecznoĈè zaczekania, aĔ serwer udzieli
odpowiedzi.
Kontrolery, dyrektywy i usĥugi
_
115
Wynik jest w postaci argumentów (o nazwach parametrów bödñcych po-
lami obiektu) przekazywany konstruktorowi.
Na koþcu funkcja
otherwise()
wskazuje domyĈlny adres URL dla przekie-
rowania, jeĈli nie nastñpi dopasowanie Ĕadnej trasy.
Byè moĔe zauwaĔyäeĈ, Ĕe kontrolery EditCtrl i NewCtrl korzy-
stajñ z tego samego szablonu, czyli views/recipeForm.html. Co siö tutaj
dzieje? Po prostu ponownie wykorzystaliĈmy szablon przezna-
czony do edycji przepisu kulinarnego. Szablon wyĈwietla róĔne
elementy w zaleĔnoĈci od wywoäanego kontrolera.
Po zakoþczeniu omawiania kontrolerów moĔemy przejĈè do szablonów.
Zobaczysz, w jaki sposób wymienione kontrolery zostaäy powiñzane z sza-
blonami, a takĔe dowiesz siö, jak zarzñdzaè danymi, które sñ wyĈwietlane
uĔytkownikowi.
Szablony
Rozpoczynamy od zapoznania siö z najbardziej zewnötrznym, gäównym
szablonem zdefiniowanym w pliku index.html. Stanowi on podstawö dla
naszej aplikacji skäadajñcej siö z pojedynczej strony, a wszystkie pozostaäe
widoki sñ wczytywane w kontekĈcie omawianego tutaj szablonu:
<!DOCTYPE html>
<html lang="pl" ng-app="guthub">
<head>
<title>GutHub - tworzenie przepisów kulinarnych i dzielenie siÚ 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>
116 _
Rozdziaĥ 4. Analiza aplikacji AngularJS
<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öè elementów, na które warto zwró-
ciè uwagö. WiökszoĈè z nich miaäeĈ okazjö poznaè w rozdziale 2. Wspo-
mniane elementy omówimy po kolei.
ng-app
Ustawienie moduäu dla aplikacji GutHub. Jest to dokäadnie ten sam
moduä, który wykorzystaliĈmy we funkcji
angular.module()
. W ten sposób
framework AngularJS wie, jak wszystko ma zostaè ze sobñ poäñczone.
script znacznik
W tym miejscu nastöpuje wczytanie AngularJS w aplikacji. Framework
trzeba wczytaè przed wszystkimi plikami JavaScript, które go uĔywa-
jñ. W idealnej sytuacji znaczniki odpowiedzialne za wczytywanie
skryptów JavaScript powinny znajdowaè siö na koþcu pliku szablonu.
butterbar
Aha! To pierwsze uĔycie naszej wäasnej dyrektywy. Ta dyrektywa zo-
staäa zdefiniowana wczeĈniej i chcemy jñ wykorzystaè wraz z elemen-
tem wyĈwietlanym podczas zmiany trasy. Po zakoþczeniu powodze-
niem operacji zmiany trasy element powiñzany z dyrektywñ
butterbar
powinien zostaè ukryty. Dyrektywa powoduje wyĈwietlenie tekstu
(w omawianym przypadku jest to nudny komunikat
Wczytywanie...
), gdy
zachodzi potrzeba.
Ècza href wartoĂci
To äñcza
href
do róĔnych stron naszej aplikacji skäadajñcej siö z poje-
dynczej strony. Zwróè uwagö na uĔycie znaku
#
gwarantujñcego, Ĕe
strona nie zostanie ponownie wczytana. Adresy sñ podawane wzglö-
dem strony bieĔñcej. Framework AngularJS monitoruje wspomniane
adresy URL (dopóki strona nie zostanie ponownie wczytana) i wykonuje
caäñ pracö zwiñzanñ z ich obsäugñ (w rzeczywistoĈci jest to bardzo
nudne zarzñdzanie trasami zdefiniowane przez nas wczeĈniej wraz
z trasami), gdy zachodzi potrzeba.
Kontrolery, dyrektywy i usĥugi
_
117
ng-view
W tym miejscu wykonywana jest pozostaäa czöĈè pracy. WczeĈniej we
fragmencie rozdziaäu poĈwiöconym kontrolerom zdefiniowaliĈmy trasy.
CzöĈciñ definicji jest adres URL trasy, powiñzany z niñ kontroler i sza-
blon. Kiedy framework AngularJS wykryje zmianö trasy, wtedy na-
stöpuje wczytanie szablonu, doäñczenie do niego kontrolera oraz za-
stñpienie elementu
ng-view
zawartoĈciñ szablonu.
Jedynñ rzeczñ rzucajñcñ siö w oczy jest brak znacznika
ng-controller
. Wiök-
szoĈè aplikacji zawiera pewnego rodzaju kontroler
MainController
powiñzany
z szablonem gäównym. Najczöstszym miejscem jego podania jest znacznik
<body>
. W omawianej aplikacji nie uĔywamy wspomnianego znacznika,
poniewaĔ caäy szablon gäówny nie zawiera treĈci AngularJS wymagajñcej
odwoäania do obiektu
scope
.
Spójrzmy teraz na szablony powiñzane z poszczególnymi kontrolerami. Na
poczñtek przyglñdamy siö szablonowi wyĈwietlajñcemu listö 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ö bardzo nudny szablon. Znajdujñ siö tutaj jedynie dwa intere-
sujñce punkty. Pierwszy to standardowy sposób uĔycia znacznika
ng-repeat
.
Zadanie wymienionego znacznika polega na pobraniu wszystkich przepisów
z obiektu
scope
, a nastöpnie ich wyĈwietleniu.
Drugi interesujñcy punkt to uĔycie znacznika
ng-href
zamiast
href
. Ma to
na celu unikniöcie wygenerowania nieprawidäowego äñcza podczas wczyty-
wania frameworka AngularJS. Znacznik
ng-href
gwarantuje, Ĕe w Ĕadnej
chwili uĔytkownikowi nie zostanie wyĈwietlony nieprawidäowy znacznik.
Wymienionego znacznika powinieneĈ uĔywaè zawsze, gdy adresy URL sñ
dynamiczne, a nie statyczne.
Byè moĔe zadajesz sobie pytanie: gdzie podziaä siö kontroler? Nie mamy
zdefiniowanego znacznika
ng-controller
i tak naprawdö nie ma zdefinio-
wanego kontrolera gäównego. W tym miejscu do gry wchodzi mapowanie
tras. MoĔe pamiötasz (mówiliĈmy o tym kilka stron wczeĈniej), Ĕe trasa /
powoduje przekierowanie do wyĈwietlajñcego listö przepisów kulinarnych
szablonu, któremu przypisano kontroler
ListCtrl
. Dlatego teĔ wszelkie od-
niesienia do zmiennych pozostajñ w zasiögu wymienionego kontrolera.
118 _
Rozdziaĥ 4. Analiza aplikacji AngularJS
Teraz przechodzimy do znacznie ciekawszego szablonu, czyli odpowiedzial-
nego za wyĈwietlenie przepisu.
<!-- Plik: chapter4/guthub/app/views/viewRecipe.html. -->
<h2>{{recipe.title}}</h2>
<div>{{recipe.description}}</div>
<h3>Skïadniki</h3>
<span ng-show="recipe.ingredients.length == 0">Brak skï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äy, przydatny szablon. Warto zwróciè uwagö na dwa punkty
powyĔszego szablonu, choè niekoniecznie w kolejnoĈci ich wymienienia.
Pierwszy to caäkiem standardowy sposób uĔycia dyrektywy
ng-repeat
.
Przepisy kulinarne znajdujñ siö w zasiögu kontrolera
ViewCtrl
wczytanego
przez funkcjö
resolve()
przed wyĈwietleniem strony uĔytkownikowi. Dziöki
temu gwarantujemy prawidäowe dziaäanie strony, gdy zostaje wyĈwietlona.
Drugi punkt to uĔycie dyrektywy
ng-submit
w formularzu. Wymieniona
dyrektywa oznacza, Ĕe wysäanie formularza spowoduje wywoäanie funkcji
edit()
obiektu
scope
. Wysäanie formularza nastöpuje, gdy klikniöty bödzie
przycisk niepowiñzany z Ĕadnñ funkcjñ (w omawianym przypadku to
przycisk Edycja). I znów dziaäanie frameworka AngularJS zostaäo zaprojek-
towane bardzo sprytnie — potrafi on prawidäowo ustaliè zasiög, do którego
ma siö odwoäywaè (na przykäad: moduäu, trasy lub kontrolera), i wywoäaè
odpowiedniñ metodö we wäaĈciwym czasie.
Teraz moĔemy przejĈè do ostatniego (i prawdopodobnie najbardziej skom-
plikowanego) szablonu, czyli formularza pozwalajñcego na dodanie lub edy-
cjö przepisu kulinarnego.
Kontrolery, dyrektywy i usĥugi
_
119
<!-- 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ï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ñ</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ñ</button>
</div>
</form>
120 _
Rozdziaĥ 4. Analiza aplikacji AngularJS
Nie panikuj! Wyglñda na to, Ĕe szablon zawiera caäkiem sporñ iloĈè kodu,
i faktycznie tak jest. Jednak po rzeczywistym zagäöbieniu siö weþ moĔna
siö przekonaè, Ĕe kod nie jest skomplikowany. Tak naprawdö to prosta,
powtarzajñca siö struktura, pokazujñca, jak edytowalne pola tekstowe zo-
staäy zastosowane w formularzu przeznaczonym do edycji przepisów ku-
linarnych.
x
W pierwszym polu tekstowym (
title
) zostaäa umieszczona dyrektywa
focus
. Dziöki temu po przejĈciu na tö stronö wskazane pole zostanie
wybrane, a uĔytkownik bödzie mógä natychmiast rozpoczñè wprowa-
dzanie danych wejĈciowych.
x
Dyrektywa
ng-submit
jest uĔyta w bardzo podobny sposób jak w po-
przednim przykäadzie, a wiöc nie bödziemy jej tutaj dokäadnie oma-
wiaè. Warto wiedzieè, Ĕe powoduje zapisanie stanu przepisu kulinarne-
go i wskazuje koniec procesu edycji. Ponadto jest powiñzana z funkcjñ
save()
zdefiniowanñ w kontrolerze
EditCtrl
.
x
Dyrektywa
ng-model
säuĔy do poäñczenia róĔnych pól tekstowych for-
mularza sieciowego z polami modelu.
x
Jednym z najbardziej interesujñcych aspektów omawianej strony jest
umieszczona w czöĈci poĈwiöconej liĈcie skäadników dyrektywa
ng-
controller
, której naprawdö warto poĈwiöciè nieco uwagi i spróbowaè
w peäni zrozumieè sposób jej dziaäania. Zobaczmy wiöc, co siö tutaj
dzieje.
Lista skäadników jest wyĈwietlana, a zawierajñcy jñ znacznik jest po-
wiñzany z dyrektywñ
ng-controller
. Oznacza to, Ĕe caäy znacznik
<ul>
znajduje siö w zasiögu kontrolera
IngredientsCtrl
. MógäbyĈ w tym
miejscu zapytaè: co z rzeczywistym kontrolerem
EditCtrl
powiñzanym
z szablonem? Jak siö okazuje,
IngredientsCtrl
jest tworzony jako kon-
troler potomny
EditCtrl
i tym samym dziedziczy po nim. Dlatego teĔ
dostöp do obiektu
recipe
nastöpuje z poziomu kontrolera
EditCtrl
.
Ponadto kontroler
IngredientsCtrl
dodaje metodö
addIngredient()
uĔy-
wanñ przez dyrektywö
ng-click
i dostöpnñ jedynie w zasiögu znacz-
nika
<ul>
. Dlaczego zdecydowaliĈmy siö na takie rozwiñzanie? To naj-
lepszy sposób na rozdzielenie obowiñzków. Po co umieszczaè metodö
addIngredient()
w kontrolerze
EditCtrl
, skoro 99% szablonu jej nie po-
trzebuje? Kontrolery potomne i zagnieĔdĔone doskonale sprawdzajñ siö
w tego rodzaju sytuacjach i pozwalajñ na oddzielenie logiki biznesowej
przez umieszczenie jej w äatwiejszych do zarzñdzania elementach.
Kontrolery, dyrektywy i usĥugi
_
121
x
Pozostaäe dyrektywy, które chcemy tutaj omówiè, sñ kontrolkami prze-
znaczonymi do weryfikacji formularza sieciowego. We frameworku
AngularJS moĔna bardzo äatwo okreĈliè, Ĕe dane pole formularza jest
wymagane. W tym celu wystarczy dodaè do tego pola dyrektywö
required
(jak to zrobiono w omawianym fragmencie kodu). Rodzi siö jednak
pytanie: co dalej?
Przechodzimy do przycisku Zapisz. Zwróè uwagö na uĔycie dyrekty-
wy
ng-disabled
, która ma wartoĈè
recipeForm.$invalid
. Czäon pierwszy
(
recipeForm
) to nazwa formularza zawierajñcego deklaracjö dyrektywy.
Framework AngularJS dodaje do niego pewne zmienne specjalne (za-
liczamy do nich
$valid
i
$invalid
) pozwalajñce na kontrolowanie ele-
mentów formularza sieciowego. AngularJS wyszukuje wszystkie wy-
magane elementy, a nastöpnie odpowiednio uaktualnia wspomniane
zmienne specjalne. JeĔeli pole säuĔñce do podania nazwy przepisu kuli-
narnego pozostanie niewypeänione, wartoĈciñ
recipeForm.$invalid
bödzie
true
(a wartoĈciñ
$valid
bödzie
false
) i przycisk Zapisz zostanie zablo-
kowany.
Istnieje równieĔ moĔliwoĈè okreĈlenia minimalnej i maksymalnej däugoĈci
pola tekstowego, a takĔe wzorzec wyraĔenia regularnego przeznaczonego
do przeprowadzenia weryfikacji danego pola. Co wiöcej, pewne funkcje
zaawansowane moĔna wykorzystaè do wyĈwietlania komunikatów bäödów
po wystñpieniu pewnych okreĈlonych warunków. Spójrzmy na prosty
przykäad:
<form name="myForm">
Nazwa uĝ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ñ uĔycia dyrektywy
ng-minlength
w powyĔszym fragmencie ko-
du zdefiniowano, Ĕe nazwa uĔytkownika musi skäadaè siö z przynajmniej
trzech znaków. Teraz formularz zostaje wypeäniony danymi pochodzñcymi
z obiektu
scope
— w omawianym przykäadzie to jedynie
userName
. Wszystkie
pola tekstowe majñ obiekt
$error
(zawiera informacje o rodzaju ewentual-
nego bäödu:
required
,
minlength
,
maxlength
lub
pattern
) oraz wäaĈciwoĈè
$valid
wskazujñcñ poprawnoĈè bñdĒ teĔ niepoprawnoĈè danych wejĈciowych.
122 _
Rozdziaĥ 4. Analiza aplikacji AngularJS
Takie rozwiñzanie pozwala na selektywne wyĈwietlanie uĔytkownikowi
komunikatu bäödu w zaleĔnoĈci od jego rodzaju, jak to pokazano w powyĔ-
szym fragmencie kodu.
Do drugiego przycisku doäñczona jest dyrektywa
ng-click
uĔywana pod-
czas usuwania przepisu kulinarnego. Zwróè uwagö, Ĕe przycisk jest wy-
Ĉwietlany tylko wtedy, gdy przepis nie zostaä jeszcze zapisany. Wprawdzie
znacznie sensowniejsze wydaje siö uĔycie
ng-hide="recipe.id"
, ale czasami
bardziej semantyczne rozwiñzanie to
ng-show="!recipe.id"
. Oznacza to wy-
Ĉwietlenie przycisku, gdy przepis kulinarny nie zawiera identyfikatora,
zamiast ukrywania przycisku, jeĈli przepis ma zdefiniowany identyfikator.
Testy
WstrzymywaliĈmy siö z przedstawieniem testów wraz z kontrolerami, ale
musiaäeĈ siö spodziewaè, Ĕe kiedyĈ wreszcie do nich przejdziemy. W tym
podrozdziale zaprezentowane zostanñ testy, które naleĔy utworzyè dla
przygotowanego dotñd fragmentu kodu. Dowiesz siö równieĔ, jak tworzy
siö takie testy.
Testy jednostkowe
NajwaĔniejszy rodzaj testów to testy jednostkowe. Pozwalajñ one na spraw-
dzenie, czy opracowane kontrolery (dyrektywy i usäugi) majñ prawidäowñ
strukturö i konstrukcjö oraz czy dziaäajñ zgodnie z oczekiwaniami.
Zanim przejdziemy do poszczególnych testów jednostkowych, warto spoj-
rzeè na szkielet przeznaczony dla wszystkich testów jednostkowych doty-
czñcych kontrolera:
describe('Kontrolery', function() {
var $scope, ctrl;
// W teĞcie naleĪy wskazaü moduá.
beforeEach(module('guthub'));
beforeEach(function() {
this.addMatchers({
toEqualData: function(expected) {
return angular.equals(this.actual, expected);
}
});
});
describe('ListCtrl', function() {....});
// Miejsce na opisanie pozostaáych kontrolerów.
});
Testy
_ 123
Przygotowany szkielet (tutaj nadal wykorzystujemy styl Jasmine do tworze-
nia testów) wykonuje kilka zadaþ.
1.
Tworzy globalnie (przynajmniej dla testu) dostöpny obiekt
scope
i kon-
troler, a wiöc nie trzeba siö przejmowaè tworzeniem nowej zmiennej
dla kaĔdego kontrolera.
2.
Inicjalizuje moduä uĔywany przez aplikacjö (w omawianym przykäadzie
jest to GutHub).
3.
Dodaje specjalne dopasowanie nazywane
equalData
. Pozwala ono na prze-
prowadzanie asercji na obiektach Ēródäa (na przykäad przepisach kuli-
narnych) zwracanych przez usäugö
$resource
lub na wywoäanie RESTful.
Pamiötaj o koniecznoĈci dodania specjalnego dopasowania na-
zywanego equalData za kaĔdym razem, gdy zachodzi potrzeba
stosowania asercji na zwróconych obiektach ngResource. WiñĔe
siö to z faktem, Ĕe zwrócone obiekty ngResource majñ metody do-
datkowe, których zwykäe wykonanie zakoþczy siö niepowodze-
niem, poniewaĔ oczekiwane sñ wywoäania equalData.
Majñ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áuĪy do odróĪnienia
// zmiennych wstrzykniĊ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Ê lista przepisów kulinarnych', function() {
expect($scope.recipes).toEqual([1, 2, 3]);
});
});
Jak zapewne pamiötasz, kontroler
ListCtrl
naleĔy do najprostszych w apli-
kacji. Konstruktor kontrolera pobiera po prostu listö przepisów, a nastöp-
nie zapisuje je w obiekcie. Wprawdzie moĔna do tego utworzyè test, ale
wydaje siö to zbödne. W omawianym przykäadzie mimo wszystko utwo-
rzyliĈmy test, poniewaĔ testy jednostkowe sñ wspaniaäe!
124 _
Rozdziaĥ 4. Analiza aplikacji AngularJS
Znacznie ciekawiej robi siö w przypadku usäugi
MultiRecipeLoader
. Wy-
mieniona usäuga jest odpowiedzialna za pobranie listy przepisów kulinar-
nych z serwera i przekazanie ich jako argumentu (kiedy zastosowana jest
prawidäowa konfiguracja za pomocñ usäugi
$route
):
describe('MultiRecipeLoader', function() {
var mockBackend, recipe, loader;
// _$httpBackend_ to nazwa taka sama jak $httpBackend. Zastosowany zapis sáuĪy do odróĪnienia
// zmiennych wstrzykniĊtych od zmiennych lokalnych.
beforeEach(inject(function(_$httpBackend_, Recipe, MultiRecipeLoader) {
recipe = Recipe;
mockBackend = _$httpBackend_;
loader = MultiRecipeLoader;
}));
it('Wynikiem powinno byÊ 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áych kontrolerów.
Test usäugi
MultiRecipeLoader
odbywa siö przez przygotowanie usäugi
Http
´
Backend
w naszym teĈcie. Obiekt pochodzi z pliku angular-mocks.js i jest
doäñczany w trakcie przeprowadzania testów. Po prostu wstrzykniöcie go
do metody
beforeEach()
jest wystarczajñce, aby moĔna byäo konfigurowaè
oczekiwania. W drugim, znacznie ciekawszym teĈcie oczekiwanie zostaäo
zdefiniowane jako wywoäanie
server GET
do recipes, a wynikiem powinna
byè tablica obiektów. Nastöpnie uĔywamy dopasowania w celu spraw-
dzenia, czy uzyskany wynik jest dokäadnie zgodny z oczekiwaniami. Zwróè
uwagö na wywoäanie
flush()
w obiekcie makiety, przekazujñce odpowiedĒ
pochodzñcñ z serwera. Tego rodzaju mechanizm moĔna wykorzystaè do
przetestowania przepäywu kontroli i sprawdzenia, jak aplikacja dziaäa przed
otrzymaniem odpowiedzi z serwera i po jej otrzymaniu.
Pomijamy kontroler
ViewCtrl
, poniewaĔ jest niemal identyczny z
ListCtrl
,
poza dodatkiem w postaci metody
edit()
. Wymieniona metoda jest bardzo
äatwa do przetestowania: wystarczy wstrzyknñè usäugö
$location
do testu
i sprawdziè jej wartoĈè.
Testy
_ 125
Przechodzimy teraz do kontrolera
EditCtrl
, który z perspektywy testów
jednostkowych ma dwa interesujñce punkty. Funkcja
resolve()
jest podobna
do uĔywanej juĔ poprzednio i moĔe byè przetestowana w dokäadnie ten
sam sposób jak wczeĈniej w rozdziale. Zamiast tego zobaczysz teraz, jak
moĔna przetestowaè metody
save()
i
remove()
. Spójrzmy wiöc na wymienione
testy (przyjöto zaäoĔenie o uĔyciu szkieletu przedstawionego w poprzednim
przykäadzie):
describe('EditCtrl', function() {
var mockBackend, location;
beforeEach(inject(function($rootScope,
$controller,
_$httpBackend_,
$location,
Recipe) {
mockBackend = _$httpBackend_;
location = $location;
$scope = $rootScope.$new();
ctrl = $controller('EditCtrl', {
$scope: $scope,
$location: $location,
recipe: new Recipe({id: 1, title: 'Przepis'})
});
}));
it('Wynikiem powinien byÊ zapisany przepis kulinarny', function() {
mockBackend.expectPOST('/recipes/1',
{id: 1, title: 'Przepis'}).respond({id: 2});
// Ustawienie innej wartoĞci, aby mieü gwarancjĊ jej zmiany podczas testu.
location.path('test');
$scope.save();
expect(location.path()).toEqual('/test');
mockBackend.flush();
expect(location.path()).toEqual('/view/2');
});
it('Wynikiem powinno byÊ usuniÚcie przepisu kulinarnego', function() {
expect($scope.recipe).toBeTruthy();
location.path('test');
$scope.remove();
expect($scope.recipe).toBeUndefined();
expect(location.path()).toEqual('/');
});
});
126 _
Rozdziaĥ 4. Analiza aplikacji AngularJS
W pierwszym teĈcie sprawdzane jest dziaäanie funkcji
save()
. W szczegól-
noĈci upewniamy siö, Ĕe operacja zapisu powoduje wykonanie do serwera
Ĕñdania
POST
wraz z obiektem. Nastöpnie, po udzieleniu odpowiedzi przez
serwer, przechodzimy na stronö zawierajñcñ nowo zapisany przepis kulinarny.
Drugi test jest jeszcze prostszy. Po prostu sprawdzamy, czy wywoäanie
funkcji
remove()
powoduje usuniöcie wskazanego przepisu kulinarnego,
a nastöpnie przekierowujemy uĔytkownika na stronö docelowñ. Jest to äatwe
do wykonania dziöki wstrzykniöciu usäugi
$location
do testu i jej uĔyciu.
Pozostaäa czöĈè testów jednostkowych dla kontrolerów wykorzystuje te same
wzorce, a wiöc moĔna je tutaj pominñè. Ogólnie rzecz ujmujñc, testy jed-
nostkowe opierajñ siö na kilku aspektach:
x
zagwarantowanie, Ĕe kontroler (lub bardziej prawdopodobnie obiekt
scope
) osiñgnie prawidäowy stan na koþcu procesu inicjalizacji;
x
potwierdzenie wykonania prawidäowych wywoäaþ serwera i osiñgniöcie
wäaĈciwego stanu przez obiekt
scope
w trakcie wspomnianych wywo-
äaþ serwera oraz po ich zakoþczeniu (do tego celu w testach jednost-
kowych uĔywany jest obiekt makiety);
x
wykorzystanie funkcji wstrzykiwania zaleĔnoĈci we frameworku An-
gularJS, aby uzyskaè uchwyt do elementów i obiektów uĔywanych przez
kontroler. Pozwala to upewniè siö, Ĕe kontroler ustawia prawidäowy stan.
Testy scenariuszy
Gdy testy jednostkowe zakoþczñ siö powodzeniem, moĔe pojawiè siö po-
kusa zakoþczenia pracy. Jednak praca programisty AngularJS nie koþczy
siö, zanim nie bödñ przeprowadzone testy scenariuszy. Wprawdzie testy
jednostkowe dajñ gwarancjö, Ĕe kaĔdy najmniejszy fragment kodu Java-
Script dziaäa zgodnie z oczekiwaniami, ale jednoczeĈnie warto siö upewniè
o wczytaniu szablonów, prawidäowym powiñzaniu kontrolerów i poprawnej
reakcji na klikniöcia elementów szablonu.
Dokäadnie do tego celu säuĔñ testy scenariuszy we frameworku AngularJS.
Pozwalajñ one na:
x
wczytanie aplikacji;
x
przejĈcie na konkretnñ stronö;
x
klikniöcie i wprowadzenie tekstu;
x
upewnienie siö o prawidäowej reakcji aplikacji.
Testy
_ 127
Na jakiej zasadzie dziaäa test scenariusza dla strony wyĈwietlajñcej listö
przepisów kulinarnych? Przede wszystkim przed rozpoczöciem rzeczywi-
stego testu trzeba poczyniè pewne przygotowania.
Aby ten test scenariusza dziaäaä, konieczne jest przygotowanie serwera
WWW, który bödzie miaä moĔliwoĈè akceptacji Ĕñdaþ wykonywanych
przez aplikacjö GutHub, a takĔe pobierania listy przepisów kulinarnych
z testowanej aplikacji oraz ich przechowywania. MoĔesz zmodyfikowaè kod
i wykorzystaè zapisywanñ w pamiöci listö przepisów — wymaga to usu-
niöcia
$resource
dla przepisu i zmiany usäugi na obiekt zawierajñcy dane
w formacie JSON. Ewentualnie moĔna ponownie wykorzystaè i zmodyfiko-
waè serwer WWW przedstawiony w poprzednim rozdziale bñdĒ teĔ uĔyè
narzödzia Yeoman!
Po przygotowaniu i uruchomieniu serwera WWW obsäugujñcego aplikacjö
wystarczy utworzyè i wykonaè przedstawiony poniĔej test:
describe('GutHub App', function() {
it('Wynikiem powinna byÊ lista przepisów kulinarnych', function() {
browser().navigateTo('/index.html');
// DomyĞlna lista przepisów kulinarnych w aplikacji GutHub skáada siĊ jedynie z dwóch pozycji.
expect(repeater('.recipes li').count()).toEqual(2);
});
});
217
Skorowidz
A
analiza aplikacji, 101
anatomia aplikacji, 23
API, 150
API HTML5, 174
API jQuery, 165
aplikacje
AJAX, 57
mobilne, 61
sieciowe, 11
architektura MVC, 14, 24
arkusze stylów CSS, 39
asynchroniczne wywoäania metod, 130
atak typu XSRF, 147
atrybut
href, 42
multiple, 203
ng-app, 19
ng-change, 30
ng-controller, 44
ng-model, 20, 45, 193
ng-repeat, 19, 37
required, 66
src, 42
B
bezpieczeþstwo, 146
biblioteka
jQuery, 110
NodeJS, 76
RequireJS, 92
Socket.IO, 76, 129, 204
blok
Config, 179
Run, 179
bäödy, 173
buforowanie odpowiedzi, 134
C
ciasteczka, 184
D
dane wejĈciowe, 65
debugowanie, 85
definiowanie kontrolerów, 111
deklaracja
dyrektywy, 201
kontrolera, 194
zasobu, 140
dodawanie trasy, 90
doäñczanie danych, 14, 20, 27
DOM, Document Object Model, 14, 63, 164
dostöp do
konsoli, 87
zasiögu, 160
dwukropek, 140
dyrektywa, 17, 149
butterbar, 109, 116, 211
errorMessage, 212
expander, 167
focus, 65, 95, 109
ng-app, 24
ng-bind, 29
ng-bind-html, 189
ng-bind-html-unsafe, 189
ngbkFocus, 65
ng-class, 40
ng-click, 65, 122
ng-controller, 120
ng-disabled, 67
ng-hide, 38
ng-minlength, 121
ng-model, 120
ngPluralize, 186
ng-repeat, 37, 41, 118, 196, 200
218 _ Skorowidz
dyrektywa
ng-style., 40
ng-submit, 32, 118, 120
ng-view, 58
dyrektywy
dostöp do zasiögu, 160
funkcja compile(), 157
funkcja link(), 157
nazwa, 152
obsäuga zdarzeþ, 32
opcja priority, 153
opcja templates, 154
opcja transclude, 157
wäaĈciwoĈè restrict, 152
dyskretny kod JavaScript, 33
dziaäanie
dyrektywy, 110
filtru, 57
funkcji save(), 126
opcji dyrektywy, 164
testu, 99
usäugi $location, 174
E
edycja przepisu, 118
element
<input>, 66
ng-app, 116
ng-href, 117
ng-repeat, 117
ng-show, 38
ng-view, 117
elementy
drzewa DOM, 36, 164
powtarzalne, 36
szablonu, 116
tablicy, 42
F
faza
kompilacji, 158
äñczenia, 158
filtr, 55
filterService, 200
linky, 189, 190
filtrowanie, 196
daty i godziny, 188
listy, 80
formatowanie danych, 55
formularz, 29
formularz rejestracyjny, 65
framework
BDD, 79
Express, 75
funkcja
$http.get(), 130
$resource, 140
$scope. safeApply(), 173
$watch(), 30, 45–50
addExpander(), 168
callback, 208
callMe(), 50
compile(), 158
computeNeeded(), 31
controller(), 165
directive(), 63
done, 203
edit(), 118
equals(), 143
factory(), 53, 181
focus(), 64, 110
inheritedData(), 165
injector(), 165
link(), 64, 158
otherwise(), 58, 115
provider(), 53, 181
remove(), 21, 112
resolve(), 114, 118, 125
run(), 156
save(), 112
scope(), 165
select(), 194
selectRow(), 41
service(), 53, 181
StartUpController(), 31
stun(), 39
then(), 107, 143
totalCart(), 47
funkcje
jQuery, 165
typu getter, 175
typu setter, 175
usäugi $location, 174
Skorowidz
_ 219
G
granice aplikacji, 24
grupowanie zaleĔnoĈci, 51
H
harmonijka, 168
hermetyzacja, 142
I
IDE, 73
identyfikator lokalizacji, 186
informacje o lokalizacji, 173
instalacja
Karma, 96
Yeoman, 89
integracja z IDE, 79
interceptor, 212
interfejs
document.cookie, 184
uĔytkownika, 19
internacjonalizacja aplikacji, 185, 188
K
karta
kredytowa, 139
Model, 86
Performance, 86
katalog
app, 93
config, 93
test, 93
klasa HelloController, 13
kod
lokalizacji, 186
serwera, 75
usäugi, 105
kompilacja, 82
kompilator Closure Compiler, 83
komponent nasäuchujñcy, 183
komunikacja
miödzy kontrolerami, 196
miödzy zasiögami, 182
z serwerami, 61, 129
z usäugami, 211
konfiguracja
moduäu, 180
routingu, 113
Ĉrodowiska programistycznego, 92
testów jednostkowych, 96
zasiögu, 161
Ĕñdania, 131
konstruktor kontrolera, 167
kontroler, 14, 25, 43, 103, 166
CartController, 21, 47
EditCtrl, 111, 120, 125
FilterCtrl, 196
HelloController, 13
IngredientsCtrl, 113
ListCtrl, 111, 117, 123, 196
NamesListCtrl, 136
NewCtrl, 112
RootController, 212
SearchController, 185
ViewCtrl, 111
kontrolka datepicker, 191, 194
koszyk na zakupy, 18, 48
L
lista, 36
logika
aplikacji, 14, 65
biznesowa, 142
lokalizacja, 185, 186
luka w zabezpieczeniach, 146
Ĥ
äñcza
bezwzglödne, 177
href, 116
wzglödne, 177
M
menedĔer NPM, 77
metoda, Patrz funkcja
metody
konfiguracji moduäu, 180
moduäu AngularJS, 178
obiektu Recipe, 106
obiektu zdarzenia, 184
220 _ Skorowidz
model, 14, 25, 102
moduä
gäówny, 179
guthub, 114
ngResource, 138
ngSanitize, 189
Sanitize, 188
modyfikacja
ciasteczka, 148
Ĕñdania, 132
monitorowanie
elementów, 50
zmian, 45
MVC, 14, 24
N
nagäówek
Authorization, 213
DO NOT TRACK, 133
nagäówki
HTTP, 133
uwierzytelnienia, 211
narzödzia, 73, 84
narzödzie
Ant, 92
Batarang, 85
karta Model, 86
karta Performance, 86
wäaĈciwoĈci elementów, 87
zaleĔnoĈci usäugi, 87
Karma, 76–78, 96
RequireJS, 92
Scenario Runner, 80, 82
WebStorm, 73
Yeoman, 70, 75
dodawanie tras, 90
funkcje, 88
instalacja, 89
testy, 91
tworzenie projektu, 90
nasäuchiwanie zdarzeþ, 183
nawias klamrowy, 28
nazwa dyrektywy, 152
ngResource, 142
notacja
{{ }}, 20
interpolacji, 39
NPM, Node Package Manager, 77
O
obiekt
$scope, 44
config, 131, 135
Recipe, 106
resolve, 114
scope, 136
zdarzenia, 184
obiektowy model dokumentu, 14
obiekty wstrzymane, 107
obietnica, 107, 143
obsäuga
bäödów, 145, 210
HTML5, 63
kodów stanu, 212
liczby mnogiej, 186
lokalizacji, 187
äñczy, 176
przekierowaþ, 211
RequireJS, 96
zdarzeþ, 32, 33
ochrona przed lukñ, 147
oczyszczanie kodu HTML, 188
opakowanie kontrolki jQuery, 191
opcje
dyrektywy, 151
wäaĈciwoĈci require, 167
operacje
bitowe, 42
logiczne, 42
matematyczne, 42
po stronie serwera, 142
optymalizacja, 83
Simple, 83
zaawansowana, 83
organizacja projektu, 70, 92
P
pasek
nawigacyjny, 35
tytuäu, 162
plik
angular.js, 187
app.js, 94
controller.js, 60
controllers.js, 64, 95
Skorowidz
_ 221
detail.html, 59
index.html, 58, 64, 95
karma.config.js, 78
list.html, 59
main.js, 95, 98
pliki
aplikacji, 70
JavaScript, 70
konfiguracyjne, 72, 78
szablonów HTML, 71
pokazywanie elementów, 38
pole
combo, 200
tekstowe, 29, 120
wyboru, 200
wyszukiwania, 199
porównania, 42
prawo Demeter, 17
produkty, 55
programowanie, 69
projekt jQuery-File-Upload, 201
prototypowe dziedziczenie, 26
przechwycenie odpowiedzi, 145
przedstawianie danych, 14
przekazywanie plików, 201
przenoszenie treĈci, 157
przepisy kulinarne, 102
przepisywanie äñczy, 177
przycisk zerowania, 32
publikacja danych modelu, 44
S
Scenario Runner, 80, 82
schematy weryfikacji HTML, 150
serwer
Karma, 78
RESTful, 106
Socket.IO, 206
WWW, 75, 90
serwis GitHub, 62
strategie wiñzania, 161
strefy czasowe, 188
stronicowanie, 207
styl Jasmine, 80, 123
style CSS, 39
szablon, 15, 17, 27, 54, 103
szablon po stronie klienta, 12
Ļ
ĈcieĔki app/img, 71
Ĉrodowisko IDE, 73, 79
T
tabela, 36
tablica currentPageItems, 209
TDD, Test-driven development, 76
technika TDD, 76, 80
test, 99
ACID, 35
integracji, 72, 80
jednostkowy, 72, 79, 122, 135, 142
kontrolera, 136
metody, 125
scenariusza, 126, 127
typu E2E, 72, 80, 99
usäugi, 124, 208
testowanie, 76
token, 148
transformacje
odpowiedzi, 134
Ĕñdania, 134
trasa, 44, 57, 114
tryb
hashbang, 175
HTML5, 175
tworzenie
aplikacji sieciowych, 11
dyrektyw, 109, 150
filtrów, 56
funkcji dyrektywy, 63
interfejsu uĔytkownika, 14
obiektu wstrzymanego, 107
obietnic, 108
paska, 162
projektu, 90, 91
szablonu, 12, 115
trasy, 57
usäug, 53
zasiögu, 160
typ zasiögu, 161
222 _ Skorowidz
U
uaktualnianie listy, 196
ukrywanie
bäödów, 212
elementów, 38
uruchamianie
aplikacji, 28, 75
serwera, 90
testów, 91
röczne, 81
zautomatyzowane, 81
usäuga, 53, 105
$cookies, 185
$cookieStore, 185
$http, 61, 129, 137, 142
$httpProvider, 135
$location, 17, 57, 124, 171
funkcje, 174
integracja AngularJS, 173
integracja HTML5, 174
tryb hashbang, 175
tryb HTML5, 175
$q, 143
$route, 57, 124
$routeProvider, 57
Authentication, 213, 214
Error, 211
errorService, 211
filterService, 196
MultiRecipeLoader, 124
Pagination, 210
Paginator, 208
stronicowania, 207
uwierzytelnienie Ĕñdania, 213
uĔycie
atrybutu ng-model, 45
biblioteki Socket.IO, 204
dyrektywy, 153
dyrektywy focus, 65
filtrów, 196
funkcji $watch(), 45, 48
kontrolerów, 43, 166
Pythona, 76
Scenario Runner, 82
serwera WWW, 75, 81
transformacji, 135
usäugi $location, 174
WebStorm, 74
wyraĔenia, 45
Yeoman, 75
zasobów AngularJS, 138
W
wczytywanie
moduäu, 179
skryptu, 23
weryfikacja
danych wejĈciowych, 65
kodu HTML, 149
pól formularza, 66
wiñzanie select, 193
widok, 14, 25, 44
wielokrotne
uĔycie kodu, 170
uĔycie komponentów, 170
wäaĈciwoĈci
AngularJS, 89
elementów, 87
obiektu, 42
obiektu zdarzenia, 184
wäaĈciwoĈè
$scope.isDisabled, 40
$valid, 66
innerHtml, 15
require, 167
window.location, 171, 173
wskazanie
atrybutu select, 195
lokalizacji, 57
wstrzykiwanie
usäugi, 96
zaleĔnoĈci, 16, 126
wtyczka
Batarang, 85
FileUpload, 203
wydajnoĈè aplikacji, 83
wykonywanie testów, 98
wyraĔenia, 42
wyĈwietlanie
listy, 196
tekstu, 28, 118
wywoäania
API, 180
zwrotne, 141
Skorowidz
_ 223
wywoäanie
$apply, 172
AngularJS, 23
factory(), 180
provider(), 181
scope.$apply, 172
select(), 194
service(), 181
wyzerowanie wartoĈci pola tekstowego, 32
wzorzec Singleton, 183
X
XHR, 130
XSRF, 147
Z
zabezpieczenia JSON, 146
zagnieĔdĔenie zasiögów, 86
zaleĔnoĈci, 51, 71, 179
RequireJS, 94
usäugi, 87
zarzñdzanie
danymi, 14
moduäami, 179
zaleĔnoĈciami, 92
zasada minimalnej wiedzy, 17
zasiög, 44, 86, 160, 182
globalny, 26
gäówny $rootScope, 182
nadrzödny, 161, 183
odizolowany, 160
potomny, 161, 183
zasoby
AngularJS, 138
RESTful, 106, 137
statyczne, 71
zasób karty kredytowej, 139
zdarzenia zasiögu, 183
zdarzenie
click, 166
loginRequired, 211
on-select, 194
select, 195
zintegrowane Ĉrodowisko
programistyczne, IDE, 73
zlokalizowany zestaw reguä, 187
zmiana
elementów drzewa DOM, 63
widoków, 57
znacznik, Patrz element
znacznik semantyczny, 34
znak
dolara, 53
dwukropka, 140
ś
Ĕñdanie, 131
CreditCard, 139
DELETE, 138
GET, 138
POST, 107, 138
XHR, 130
Ĕywe szablony, 74
O autorach
Brad Green
pracowaä nad projektem AngularJS jako menedĔer inĔynierów w Google,
gdzie jest odpowiedzialny równieĔ za kwestie zwiñzane z uäatwieniami dostöpu i pomoc
technicznñ. Przed podjöciem pracy w Google zajmowaä siö tworzeniem witryn interne-
towych dla urzñdzeþ przenoĈnych w AvantGo, zakäadaä i sprzedawaä start-upy, a kilka
lat spödziä na mozolnej pracy osoby organizujñcej przyjöcia i bankiety. Wkrótce po
ukoþczeniu szkoäy podjñä swñ pierwszñ pracö w zaäoĔonej przez Steve’a Jobsa firmie
NeXT Computer, w której odpowiadaä za przygotowywanie wersji demo oprogramowa-
nia i slajdów do prezentacji Jobsa. Brad wraz z Ĕonñ i dwójkñ dzieci mieszka w Mountain
View w stanie Kalifornia.
Shyam Seshadri
jest wäaĈcicielem i szefem Fundoo Solutions (http://www. befundoo.com/),
gdzie czas dzieli miödzy pracö nad innowacyjnymi i ekscytujñcymi nowymi produktami
na rynek indyjski a Ĉwiadczeniem usäug konsultingowych i prowadzeniem warsztatów
dotyczñcych AngularJS. Przed utworzeniem Fundoo Solutions zdobyä tytuä magistra na
prestiĔowej indyjskiej uczelni Indian School of Business w Hajdarabadzie. Po ukoþcze-
niu szkoäy pracowaä dla Google, gdzie zajmowaä siö róĔnorodnymi projektami, miödzy
innymi Google Feedback (pierwsza aplikacja oparta na frameworku AngularJS!), oraz
innymi narzödziami i projektami wewnötrznymi. Obecnie pracuje w swoim biurze w Nawi
Mumbaj w Indiach.
Kolofon
Zwierzö widniejñce na okäadce ksiñĔki AngularJS to Lactoria fornasini — morska ryba roz-
dymkoksztaätna naleĔñca do rodziny kosterowatych (Ostraciidae) i rodzaju Lactoria. ēyje na
skalistych rafach lub piaszczystym dnie, schowana i zaplñtana pomiödzy gñbkami oraz
wodorostami. Wystöpuje w zachodniej czöĈci oceanów Spokojnego i Indyjskiego. ēywi
siö gäównie robakami i innymi bezkrögowcami.
Lactoria
osiñga 15 centymetrów däugoĈci i od 3 do 50 centymetrów szerokoĈci. Osobniki za-
liczane do rodziny kosterowatych wyróĔniajñ siö szeĈciokñtnym wzorem päytek kost-
nych tworzñcych pancerz na ich skórze. Z ciaäa, przypominajñcego trójkñtne pudeäko,
wystajñ päetwy i ogon umoĔliwiajñce im poruszanie siö. Wraz z wiekiem ksztaät ciaäa koste-
rowatych zmienia siö od nieco zaokrñglonego do kwadratowego, a kolor skóry staje siö
bardziej jaskrawy.
Lactoria fornasini
chroni siö przed niebezpieczeþstwem, wydzielajñc przez skórö substan-
cje powierzchniowo czynne uruchamiane pod wpäywem stresu. Toksyny, zwykle wydalane
w postaci Ĉluzu, przenikajñ do Ĉrodowiska, draĔniñc inne ryby i organizmy znajdujñce siö
w niedalekim sñsiedztwie.
Rysunek na okäadce pochodzi ze zbiorów Johnson’s Natural History. Czcionka na okäadce
to Adobe ITC Garamond. Czcionka tekstu to Adobe Minion Pro, czcionka w nagäów-
kach — Adobe Myriad Condensed, kody zostaäy napisane czcionkñ Dalton Maag’s
Ubuntu Mono.