Začínáme s AngularJS

Dnešní díl bude věnován populárnímu MVC frameworku AngularJS. Představíme si výhody jeho používání, základní pojmy, které se k němu vážou, a samozřejmě si také vytvoříme první aplikaci.
Seriál: Node.js - s JavaScriptem na serveru (13 dílů)
- JavaScript na serveru: Začínáme s Node.js 23. 11. 2010
- JavaScript na serveru: Patří budoucnost Node.js? 21. 9. 2012
- JavaScript na serveru: Architektura a první Hello World 5. 10. 2012
- JavaScript na serveru: moduly a npm 12. 10. 2012
- JavaScript na serveru: začínáme programovat e-shop 19. 10. 2012
- JavaScript na serveru: MongoDB, Mongoose a AngularJS 26. 10. 2012
- JavaScript na serveru: Testování a kontinuální integrace 2. 11. 2012
- JavaScript na serveru: REST API 9. 11. 2012
- JavaScript na serveru: implementace REST API 16. 11. 2012
- JavaScript na serveru: nástroje a dokumentace 23. 11. 2012
- Začínáme s AngularJS 30. 11. 2012
- AngularJS direktivy a testování 7. 12. 2012
- JavaScript na serveru: CoffeeScript a šablonovací systémy 14. 12. 2012
Nálepky:
Podobně jako v předchozích dílech si i dnes můžete aplikaci stáhnout přes git příkazem git checkout -f dil11
, popř. projít zdrojové kódy na Githubu (vše je ve složce public). Kromě toho si můžete celou aplikaci prohlédnout v demoverzi.
Ačkoliv je seriál věnován převážně Node.js, nelze se vyhnout technologiím a nástrojům, které patří do jeho ekosystému. Velmi často se v Node.js vytvářejí aplikace, které používají hodně klientského JavaScriptu a komunikují se serverem přes API. Ostatně to je i případ vyvíjené aplikace v rámci tohoto seriálu. Pro tyto případy je nutností použít některý z frameworků, který programátora odstíní od rutinních úkolů a zjednoduší mu práci. A jedním z těchto frameworků je i AngularJS.
AngularJS samozřejmě není jediný framework svého druhu. Přehled všech významných MVC frameworků přináší projekt TodoMVC, kde je v každém frameworku vytvořena naprosto stejná aplikace. Můžete si prohlédnout kódy aplikace ve všech frameworcích a rozhodnout se pro ten, který se vám líbí nejvíce. I kdybyste nakonec preferovali jiný framework, je dobré poznat blíže AngularJS, protože přináší mnoho inovativních myšlenek.
AngularJS lze výborně kombinovat s populárními JavaScriptovými knihovnami jako jQuery nebo Google Closure. Příkladem jsou projekty AngularUI nebo NGX library od LMC.
Přednosti AngularJS a základní pojmy
AngularJS obsahuje řadu zajímavých myšlenek, přičemž mezi nejužitečnější koncepty frameworku se obvykle uvádí Two Way Data-Binding, implementace Dependency Injection, testovatelnost, direktivy a znovupoužitelnost komponent. V dalším textu si vysvětlíme, co si pod danými pojmy představit.
Two Way Data-Binding
Jestliže se zeptáte programátorů, kteří pracují s Angularem, jakou vlastnost považují vůbec za nejužitečnější, nejčastěji vám odpoví, že koncept Two Way Data-Binding, který řeší synchronizaci stavů mezi modelem a view. Do češtiny bychom mohli pojem přeložit jako “dvoucestná synchronizace dat”.
Oč jde? Představme si jednoduchou aplikaci, ve které máme jedno textové pole a pod ním chceme vypsat, co uživatel do pole napsal. Živá ukázka je k dispozici v dokumentaci frameworku, vše vypadá takto:

Ve chvíli, kdy uživatel napíše nějaký řetězec do textového pole, se tento text přenese do modelu (to je ta první cesta) a následně se z modelu přepíše do ostatních částí view (to je ta druhá cesta). To lze samozřejmě řešit i v jiných frameworcích, ale v případě AngularJS je tohle výborně zautomatizováno. Zdrojový kód ukázky vypadá takto:
<p> Enter name: <input type="text" ng-model="name"><br> Hello <span ng-bind="name"></span>!</p>
Textovému inputu přidáváme direktivu ng-model, která říká, že co uživatel napíše do pole, se přesune do modelu name
. Druhý řádek obsahuje element span
s direktivou ng-bind, která zase říká, že se do span
mají vypsat data z modelu name
. To je vše. Nic víc není potřeba!
Jedná se o deklarativní způsob implementace. To jest, programátor nemusí imperativně popisovat, jak data synchronizovat. Namísto toho pouze deklaruje „vztah“ a Angular zajistí synchronizaci.
Velmi dobrou ukázkou tohoto principu je spreadsheet (např. Excel), kde nadefinujete pouze vztahy, rovnice. Např. A2 = B1 + A1 a více se nestaráte. V imperativní prostředí by bylo potřeba popsat: pokud se změní B1 nebo A1, spočítej B1 + A1 a nastav výslednou hodnotu do A2.
Celý proces vyjádřen graficky vypadá takto:

Obrázek je převzat z http://docs.angularjs.org/guide/dev_guide.templates.databinding.
Na obrázku jsou zmíněny tři základní pojmy:
- template (šablona), která říká, jak aplikace má vypadat, to je to HTML v ukázce;
- model je objekt reprezentující entitu (vč. polí i primitivních typů);
- view (pohled) vznikne, když se do šablony vloží data z modelu (transformace na DOM);
Co kdybychom dvoucestnou synchronizaci řešili ručně? Vytvořili bychom handler pro událost onChange u textového pole pro funkci, která by změnu přepsala zpět do šablony, tedy v jQuery by to bylo takto:
<p> Enter name: <input type="text" id="name"><br> Hello <span id="name-value"></span>!</p>
//propsání změny $('#name').change(function(){ $('#name-value').val($(this).val()); });
Oboustranná synchronizace je u větších javascriptových aplikací velmi častá a vidíte, že i pro velmi primitivní příklad je potřeba v jQuery napsat hodně kódu. Proto se také uvádí, že aplikace v AngularJS může snížit množství kódu i na 10 % původního množství kódu. Tak to bylo v případě několika interních aplikací v Google (např. v aplikaci Feedback se díky Angularu snížil počet řádků z původních 16 000 napsaných v Javě v GWT na 1 000 řádků v JavaScriptu v Angularu).
Obsah textového pole můžeme chtít vypsat na více místech a pro každou změnu bychom museli neustále měnit obsah funkce, která data propisuje do pohledu. Dokážete představit, že udržovat takto napsanou velkou aplikaci je nesmírně náročné.
V tomhle jednoduchém případě je pouze jeden způsob, jak změnit model (uživatel změní hodnotu v inputu). Takže je potřeba sledovat tuto změnu a aktualizovat view. V případě komplexní aplikace nastanou další způsoby, jak model změnit (například dostaneme aktualizovaná data ze serveru). V tu chvíli je potřeba myslet na to, kde všude je tento model použit a je potřeba jej aktualizovat.
V případě Angularu nás to nezajímá. Pouze deklarujeme vztah a víc se o synchronizaci nestaráme. Je jedno, jak se model změnil. Výsledkem je kód, kde je logika nezávislá na prezentaci a tím pádem je snadnější ho testovat.
Drobná, i když vítaná, výhoda je také v tom, že není potřeba neustále pro všechno psát nová ID, šablony jsou tak menší a přehlednější.
Dependency Injection (DI)
O DI na Zdrojáku vycházel před rokem seriál, takže pokud náhodou nevíte, co si pod tímto pojmem představit, projděte si seriál Jak na Dependency Injection. Kromě toho je dostupná přednáška o DI v Angularu od Vojty Jíny (povídání o DI začíná na 0:37:34).
Jedná se o návrhový vzor, který řeší závislost mezi jednotlivými komponentami programu.
AngularJS obsahuje zabudovaný subsystém, které řeší DI napříč celou vyvíjenou aplikací. Umožňuje vždy přesně specifikovat, které jiné komponenty jsou uvnitř konkrétní komponenty používané.
Podívejme se na to, jak vypadají třeba deklarace controlleru pro zobrazení detailu stránky v naší aplikaci:
function PagesShowCtrl($scope, $routeParams, $location, page) { //obsah controlleru }
Controlleru se při vytvoření předávají 4 parametry, přes které se předávají závislosti. Uvnitř controlleru se nepracuje s ničím jiným než právě s těmito závislostmi. Díky tomu se všechny části aplikace výborně testují.
Jak již bylo řečeno, AngularJS obsahuje subsystém, který řeší sám předávání závislostí. Jakmile vytváří novou instanci controlleru, zkontroluje si jeho závislosti v deklaraci a všechny předá jako parametry.
Tyto závislosti se nazývají services (služby) a více informací o nich získáte v dokumentaci projektu.
Služby jsou buď interní, které jsou dodávány přímo s frameworkem, nebo si je můžeme vytvořit sami. Interní služby se prefixují znakem dolaru ($). Ve výše uvedeném případě používáme tři služby:
- $scope řeší kontext dat a je důležitý pro koncept data bindingu,
- $routeParams pracuje s parametry v URL,
- $location umožňuje práci s URL
Aplikace pracuje pouze se $scope, zapisuje/čte a binding se stará o synchronizaci mezi $scope a prezentací (DOM). Díky tomu je logika nezávislá na prezentaci a snadno se testuje.
Jako poslední parametr je v ukázce předána služba page, kterou jsme si vytvořili sami a která slouží k dotazování na REST API stránek naší aplikace.
Testovatelnost
Jedna z oblastí, na kterou je framework zaměřen, je testovatelnost kódu. Přispívá k tomu implementace DI (viz výše), ale také nástroje pro testování, které díky frameworku vznikly.
Chcete-li začít stavět novou aplikaci, je výhodné použít jako výchozí šablonu angular-seed, kterou si můžete na Githubu “forknout” a upravit pro vlastní potřeby. V projektu najdete předpřipravené prostředí pro jednotkové a end2end testy.
Díky AngularJS vznikl v Node.js projekt Testacular, který umožňuje automatizovaně spouštět testy nad různými prohlížeči. Jedná se o alternativu k JsTestDriver, o kterém jsme psali na Zdrojáku nedávno.
Díky Angularu také vznikl projekt jasmine-node, který přenesl Jasmine, jeden z nejpoužívanějších javascriptových testovacích frameworků, do prostředí Node.js.
Pro end2end testy v AngularJS existuje také scenario runner, který funguje podobně jako testování se Seleniem. V těchto testech si píšete scénář, jak má uživatel procházet aplikací, kam má třeba kliknout, napsat text a všechny reakce aplikace testuje, zda vrací správné výsledky. I když to nemusí být na první pohled zřejmé, tyto testy se píší velice rychle a lze jimi pokrýt testování důležitých častí aplikace během pár minut.
Výstup z test runneru pak může vypadat takto:
Kromě toho všeho je na stránkách projektu dostupný špičkový tutoriál, ve kterém se uživatel během chvíle naučí základy práce s Angularem a u každé ukázky je rozebráno, jak danou část testovat. Jednotkové testování i end2end testy jsou podrobně popsány v dokumentaci.
Klientská část naší aplikace bude samozřejmě také používat jednotkové i end2end testy. Blíže se tématu a nástroji Testacular budeme věnovat v příštím díle.
Direktivy
Direktivy představují způsob, jak naučit HTML novým trikům. Přes direktivy můžeme např. říct, že při kliknutí na nějaký textový element se text promění v textové pole a po kliknutí mimo tento element se nad změněnými daty provede nějaká operace (třeba se odešlou na server).
V takovém případě bychom vytvořili přes direktivy nový element (např. s názvem inlineEdit
), který by mohl v kódu vypadat takto:
<inlineEdit update='send()'>{{text}}</inlineEdit>
A k tomu v controlleru přidáme akci send()
:
function MyCtrl() { this.text = 'lorem ipsum set dolorem...'; this.send = function(text) { // … neco provede } }
Při načtení pohledu se proměnná {{text}}
změní za text “lorem ipsum set dolorem…”. Když uživatel na element klikne, promění se v textové pole. Po dokončení editace se zavolá akce send()
controlleru MyCtrl
.
Tuto direktivu jsme si vytvořili sami a její kód zde neuvádím. Je to pouze ukázka praktického příkladu, protože inline editace můžeme používat napříč celou aplikací a pokud si direktivu jednou vytvoříme, vždy pak již stačí pouze vložit nový element inlineEdit
a implementovat v controlleru akci, která bude zavolána po dokončení editace. Direktivu zmiňuji také proto, protože si její implementaci v průběhu seriálu opravdu vytvoříme a budeme používat v mnoha částech administrace.
AngularJS má opět celou řadu předpřipravených direktiv, jejich seznam s ukázkami a testy je dostupný v API dokumentaci.
Implementace
Nyní už víme, jak AngularJS funguje, můžeme se podívat, jak kód naší aplikace pro obsluhu stránek vypadá.
Bootstrap
Nejjednodušší příklad může vypadat např. takto:
<!doctype html> <html lang="en" ng-app="zdrojak"> <head> <meta charset="utf-8"> <title>Zdrojak Eshop</title> </head> <body> <div ng-view></div> <script src="//ajax.googleapis.com/ajax/libs/angularjs/1.0.2/angular.min.js"></script> <!-- další js soubory... --> </body> </html>
Takto vypadá základní šablona projektu.
Všimněte si direktivy ng-app na druhém řádku. Ta říká, že budeme chtít iniciovat projekt v AngularJS a načte modul s názvem „zdrojak“ (viz část routování).
Pak je zde ještě direktiva ng-view, která načítá šablonu pro danou routu (rozebereme dále).
Šablony
Šablony jsou klasické HTML stránky. Podívejme se, jak vypadá např. formulář pro vložení nové stránky:
<h1>Nová stránka</h1> <form class="well" ng-submit="create()"> <fieldset> <label class="control-label" for="title">Nadpis:</label> <input ng-model="page.title" class="input-xxlarge" required> <label class="control-label" for="content">Obsah:</label> <textarea ng-model="page.content" cols="300" rows="10" required></textarea> </fieldset> <div class="form-actions"> <input type="submit" value="Vytvořit" class="btn btn-primary"> </div> </form>
Zde používáme direktivu ng-submit obsahující název akce, která se zavolá při odeslání formuláře. A také direktivu ng-model, se kterou jsme se seznámili již dříve.
Zajímavá je také šablona pro výpis všech stránek v databázi:
<ul> <li ng-repeat="page in pages"><a ng-href="/pages/{{page.url}}">{{page.title}}</a></li> </ul>
Přes API získáme pole, ve kterém je každý prvek reprezentován jedním objektem stránky. Direktiva ng-repeat pole projde a pro každý objekt page
vypíše jeden element li
s titulkem stránky.
Routování
Aby framework věděl, jakou šablonu má pro jakou URL načíst, použije se služba $route poskytnutá přes $routeProvider
. Nastavení pravidel pro šablony a URL může vypadat takto:
angular.module('zdrojak', [‘zdrojakServices’]) .config(function($routeProvider) { $routeProvider.when('/', {templateUrl: '/index/index.html', controller: IndexIndexCtrl}); $routeProvider.when('/pages', {templateUrl: '/pages/index.html', controller: PagesIndexCtrl}); $routeProvider.when('/pages/new', {templateUrl: '/pages/new.html',controller: PagesNewCtrl}); $routeProvider.otherwise({redirectTo: '/'}); }) .config(function($locationProvider) { $locationProvider.html5Mode(true) } );
Vytváříme zde modul zdrojak, který se načte při inicializaci projektu (vzpomeňte na hodnotu v direktivě ng-app
). Říkáme zde, že je tento modul závislý na službě $routeProvider
, která se přidá jako parametr.
Dále pro každou routu přes metodu when()
specifikujeme:
- URL, pro které pravidlo platí
- šablonu, která se má načíst
- controller, který se má načíst a zavolat
Pokud pravidlům neodpovídá žádná URL, použije se podmínka definovaná v metodě otherwise()
, v našem případě dojde k přesměrování na úvodní stránku.
V další části chceme pracovat se službou $location, ke které se dostaneme přes $locationProvider
a říkáme zde, že chceme používat HTML5 mode pro URL, který umožní dynamicky měnit URL přes HTML5 History API (jinak by se používala verze, kdy se URL přidávají za znak #).
Controllery
V dřívější části jsme si již controllery ukázali, pojďme se podívat, jak může vypadat nejjednodušší implementace controlleru pro vkládání.
function PagesNewCtrl($scope, $location, page) { $scope.page = {}; $scope.create = function() { page.create($scope.page, function(){ $location.path('/pages'); }); } }
V controlleru používáme službu $scope
, která odkazuje na aktuálně používaný kontext aplikace (mohli bychom místo toho používat $this
). Dále vytváříme akci create()
, která bude zavolána po odeslání formuláře (ukázali jsme si ho výše) K dispozici budeme mít objekt $scope.page
a jeho položky content
a title
, které přes metodu create()
naší servisní třídy page odešleme na server. Jakmile bude stránka vložena, použijeme službu $location
, přes kterou uživatele přesměrujeme na novou adresu (služba neprovede HTTP přesměrování, pouze načte novou šablonu, změní URL a zavolá controller pro příslušnou routu).
V akci create()
neřešíme stav, kdy na serveru dojde k nějaké chybě. Také v aplikaci zatím neřešíme podporu pro obfuskaci (parametry metod se při ní změní), i na to má AngularJS nástroje. Vše doplníme později.
Services
Nakonec vytvoříme naší službu page, která bude komunikovat s API:
angular.module('zdrojakServices', ['ngResource']) .factory('page', function($resource){ return $resource('/api/v1/pages/:page', {}, { index: {method:'GET', isArray:true}, show: {method:'GET'}, create: {method:'POST'}, update: {method:'PUT'}, remove: {method:'DELETE'} }); });
Vytváříme zde službu page, který obsahuje závislost na službu $resource. Ta slouží pro komunikaci se serverem a my nad ní vytváříme pouze obal pro příjemnější a jednodušší práci v controllerech. Říkáme dále, že základní URL pro stránky je /api/v1/pages a pokud předáme službě objekt s vlastností page (viz implementace controlleru PagesDeleteCtrl()
), přidá se jeho hodnota k adrese /api/v1/pages (takže např. pro mazání stránky s ID 5 bude URL /api/v1/pages/5).
Dále předáváme objekt, ve kterém definujeme rozhraní pro komunikaci se stránkami. Vytváříme zde 5 metod, které jsou pojmenovány stejně jako akce v controlleru PageController
na straně serveru. U metody index navíc říkáme, že očekáváme vrácení pole všech stránek.
AngularJS je rozsáhlý framework, který toho nabízí ještě mnohem více. Pokud vám není některá zmíněná část frameworku zcela jasná, netrapte se tím. V dalších dílech seriálu se ke všemu ještě několikrát vrátíme.
Co dále
V příštím díle uzavřeme část seriálu, ve kterém jsme vytvářeli prostředí pro vývoj našeho e-shopu. Podíváme se mimo jiné na testování a nástroj Testacular.
Na tvorbě tohoto článku se svými připomínkami podíleli také Vojta Jína (github) a Pavel Lang (skolajs.cz). Díky!
pecka, uz aby byli dalsi, tenhle dil mi prijde sice takovej hodne uspechanej (hop sem, hop tam, ale ve vysledku nic moc) tak doufam ze ty dalsi se nad Angularem trosku pozastavi :)
diky
jinak proklikl web skolajs.cz a
503 Service Unavailable
No server is available to handle this request.
:D
Ahoj,
díky za reakci:-)
No uspěchanej…export z Google Docs toho dílu byl na 11 A4, což je myslím nejvíc z celého seriálu a veškeré aktivity kolem vydání toho článku tentokrát zabraly cca 15 hodin (napsání celého článku, neustálé přepisování částí pro větší srozumitelnost, zdrojové kódy, testování aplikace, nahrání demo verze na hosting, article & code review od několika lidí před vydáním + diskuse a zapracování připomínek do článku, převod do redakčního systému na Zdrojáku, přidání všech odkazů, nahrání obrázků, xkrát si celý text před vydáním přečíst atd.), takže obsah článku je to, co dokážu v rámci týdne, který na to vydání je, vytvořit.
Já doufám, že článek poskytl základní představu o tom, co ten framework je, co dokáže, jaké jsou jeho výhody, jak vypadají zdrojáky aplikace v něm napsané a odkazy na mnoho zdrojů. Pokud něco není srozumitelné, není problém napsat do diskuse a danou věc rozebrat, ostatně na to ty diskuse jsou.
V dalších dílech se kromě Node.js budu věnovat i Angularu v rámci vyvíjené aplikace docela dost (řekněmě 50% Node.js, 35% AngularJS, 15% zbytek). Nakonec v něm vznikne větší aplikace, takže co nebude jasné z článků, to doufám bude jasné ze zdrojáků.
Přijde mi to hodně zrychlený úvod, ale i tak pěkné :).
Jen bych doplnil, že u Service není třeba pro $resource definovat metody index, show, create, update, remove. Objekt $resource už v základu obsahuje save (POST), get (GET), query (GET/isArray), remove/delete (DELETE) a opětovná definice je celkem zbytečná práce.
A ještě pár drobných tipů, na co si dát pozor u $resource:
1) Odstraňuje lomítko na konci url. Většinou to asi nevadí, ale pokud ho server očekává, může to způsobovat problémy.
2) Pokud je v url endpointu uvedena kompletní url včetně domény a portu (http://localhost:123), tak angular bere :123 jako další proměnou a nahrazuje to standardním způsobem.
Pokusal som sa s nim riesit jeden Enterprise projekt, bohuzial je to nehorazne uzavrety (kopec anonymnych funkcii) frmwork. Je vhodny akurat tak na web pages mozno.
Resource napriklad vobec nevie pracovat s „application/x-www-form-urlencoded“.
Na standardne volania je prakticky nepouzitelny bez customizacie.
Dost som sa s nim natrapil, nakoniec som zistil, ze som zdebugol uz fakt cely jeho kod. Mozno verzia 2.0 ;-)
KAZDPADNE: IGOR MINAR ROCKS!!!! :-)
Dobry clanok o veciach ktore nemusia byt zrejme pri citani dokumentacie More AngularJS Magic to Supercharge your Webapp
Taktiez odporucam nainstalovat angularjs-batarang
Prave dokoncujem prvy projekt v angularjs (mobilny web) a som velmi spokojny s tymto fw. Je velmi produktivny a aj jednoduchy, len po uvodnom zoznameni je treba si nastudovat veci ako $digest, alebo fazy kompilacie direktiv, hlavne ak clovek zacne pouzivat jquery pluginy.
I přes nedostatečnou dokumentaci a některé mouchy je to super FW, tvoříme s ním velice rozsáhlou aplikaci, bohužel nedá se říci v něm, jelikož na to prostě nestačí, je to vhodné spíš jako templat. systém na lehčí projekty a tak musíme kombinovat – resp. nevím jak na to jinak :). (víceaplikační aplikace) Některé věci jsou vážně magie a tak doufám že se seriál o angularu zmíní pořádně do hloubky – zasloužil by si to. Každý článek natožpak v češtině je k nezaplacení. Už se těším na firefox OS + angular => možná mě po X letech zase začne bavit „programovat“ :-P
Ve větě „více informací o nich získáte v dokumentaci projektu“ vede odkaz na YouTube, ale nejspíš měl směrovat na http://docs.angularjs.org/guide/dev_guide.services.
Omlouvám se, ale můžete mi někdo vysvětlit význam následující věty?
Je ta věta vůbec česky? nebo je to nějaký „programátorský“ novotvar? Tato a podobné formulace v českých článcích z nich dělají doslova peklo. Nicméně i tak za ně děkuji, alespoň mně dokážou nasměrovat :)
Snažíte se autora „nachytat“ na jednom překlepu. Nejdříve ale zkuste Vy sám vyprodukovat tak rozsáhlý článek bez chyby. Jsem přesvědčen, že se Vám to nepodaří – když jste v těch Vašich čtyřech kratičkých větách zvládl hnedle chyby dvě.
Nový odkaz na TodoMVC je http://todomvc.com/
Našel by se návod pro nový Angular prý se oproti AngularJS liší.
Byl by prosím návod na Angular2, který je údajně přepracovaný oproti AngularJS?