Přejít k navigační liště

Zdroják » JavaScript » Začínáme s AngularJS

Začínáme s AngularJS

Články JavaScript, Různé

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.

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:

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í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!

Komentáře

Subscribe
Upozornit na
guest
12 Komentářů
Nejstarší
Nejnovější Most Voted
Inline Feedbacks
View all comments
Twista

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

koudejak

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.

Dominion

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!!!! :-)

vidya

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.

Ugo

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

Dutohlavek

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.

PeSi

Omlouvám se, ale můžete mi někdo vysvětlit význam následující věty?

Vytváříme zde službu page, který obsahuje závislost na službu $resource.

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 :)

Karel

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ě.

Jerry Klimčík

Nový odkaz na TodoMVC je http://todomvc.com/

AngularZačátečník

Našel by se návod pro nový Angular prý se oproti AngularJS liší.

AngularZačátečník

Byl by prosím návod na Angular2, který je údajně přepracovaný oproti AngularJS?

Enum a statická analýza kódu

Mám jednu univerzální radu pro začínající programátorty. V učení sice neexistují rychlé zkratky, ovšem tuhle radu můžete snadno začít používat a zrychlit tak tempo učení. Tou tajemnou ingrediencí je statická analýza kódu. Ukážeme si to na příkladu enum.

Pocta C64

Za prvopočátek své programátorské kariéry vděčím počítači Commodore 64. Tehdy jsem genialitu návrhu nemohl docenit. Dnes dokážu lehce nahlédnout pod pokličku. Chtěl bych se o to s vámi podělit a vzdát mu hold.