6. diel - Jednoduchý redakčný systém v AngularJS - API článkov
V minulej lekcii, Jednoduchý redakčný systém v AngularJS - Štruktúra projektu , sme si pripravili projektovú štruktúru pre jednoduchý redakčný systém. Ako som sľúbil, dnes sa rovno bez zbytočných rečí vrhneme na implementáciu. Takže ideme na to!
Práca s API v AngularJS
Keďže aplikácie v AngularJS je vlastne single-page aplikácie v podobe hrubého klienta, stará sa v podstate o všetko v rámci svojej MVC architektúry. Snáď jedinú výnimku tvorí dáta. Všetky dáta sa štandardne spracovávajú na strane servera (kvôli zdieľanie dát medzi klientmi a validáciu bez zásahu klienta). Server na účely získania alebo vloženie / úpravy dát vystavuje nejaké API (A pplication P rogramming I nterface).
V AngularJS potom existuje hneď niekoľko možností ako s týmto API komunikovať. Pre našu aplikáciu som zvolil API v podobe tradičného REST (Re presentational S tate T ransfer) s komunikačným formátom JSON (J ava S Cripta O bject N otation). Ak tieto skratky snáď nepoznáte, odporúčam si o tejto problematike niečo viac naštudovať, napr. V miestnom podarenom článku Stopárov sprievodca REST API
Teraz teda k možnostiam komunikácie. Základné možností, snáď ako
všade, je volať priamo jednotlivé HTTP metódy daného API. K tomu slúži
AngularJS služba $http
,
ktorá je súčasťou priamo jeho jadra. Ak si ale povieme, že naše API bude
viacmenej spĺňať štandard Restful, môžeme využiť službu $resource
,
ktorá je súčasťou modulu ngResource
, ktorá nám všetko veľmi
uľahčí. Vydáme sa teda touto cestou najmenšieho odporu
Model
Vytvoríme si službu pre obsluhu API článkov pomocou
$resource
.
App / services / articles.factory.js
Naša služba bude vyzerať nasledovne:
'use strict'; /** Model pro práci s články přes API. */ app.factory('Articles', function ($resource) { return $resource('/api/articles/:url', {url: '@url'}); });
A to je všetko, ideme domov Vážne, teraz vieme obsluhovať celej Restful API pre články,
pomocou štandardizovaných metód v rámci $resource
. Ale dobrá,
poďme si to ešte trochu vysvetliť.
V prvom rade, prečo sme použili factory
miesto
service
a aký je v tom rozdiel? Oboje definuje službu. Ovšem
klasická service
je už priamo funkciou danej služby, keď to
factory
vytvára a vracia nejaký nový objekt, ktorý onú službu
definuje. Ovšem nenechajte sa zmiasť, oboje tvoria iba jednu
inštanciu danej služby, ktorá bude k dispozícii cez DI.
V tomto prípade teda konštrukcie $resource
nad danou URL
adresou API vytvára nový objekt, ktorý keď vrátime z factory
,
bude definovaný ako služba v našej aplikácii. Chytré, že?
Inak URL adresa API tu má jeden dynamický parameter, čo bude URL adresa článku. Tento formát je práve predpísaný normou Restful API.
Dobre, máme teda vysvetlené, ale stále tu zostáva jeden problém ... To API predsa neexistuje! : -`
Tu začína tá ťažká časť, pretože ho musíte implementovať. Môžete si v podstate vybrať ktorýkoľvek programovací jazyk, nastaviť webový server a hurá do práce!
Ak sa vám ale nechce teraz odchádzať k serverovej implementáciu, existuje
ešte jeden variant, podporovaná priamo v AngularJS. Môžeme si serverové API
našej aplikácie nasimulovať v rámci lokálnej aplikácie!
Čo to znamená? API budeme musieť stále implementovať, ale urobíme to ako
súčasť našej klientskej aplikácie, kde následne využijeme modul
ngMockE2E
, ktorý nám umožní premapovať API otázky na volanie
lokálne JS implementácie. Tejto metodiky sa v praxi využíva hlavne pre
testovanie, ale občas aj pokiaľ napr. API nie je ešte hotové, ale vy s ním
v aplikácii potrebujete pracovať. Tak ideme na to!
Simulácia API
Ako som opísal vyššie, musíme v podstate vytvoriť implementácii podobnú tej serverové, akurát v JS a následne ju premapovať v rámci AngularJS. Táto implementácia samotná teda nemá s AngularJS poťažmo nič spoločné, takže ak nemáte úplne radi čistý JS, pripomínam, že ju môžete pokojne urobiť priamo na serveri vo vašom obľúbenom jazyku.
Ja teda budem predpokladať, že JS viete dobre a nebudem sa zdržovať zbytočným vysvetľovaním. Prípadne si môžete odskočiť k miestnym kurzom JavaScriptu.
Adresárová štruktúra API
Najskôr si vytvoríme bokom adresárovú štruktúru pre naše API:
api/
entities/
article.js
- Entita reprezentujúce článok.
model/
article-model.js
- Model pre prácu s článkami.
api.js
- Tu umiestnime mapovanie API v rámci našej AngularJS aplikácie.
index.html
:
... <!-- Simulace serverového API. --> <script src="api/api.js"></script> <script src="api/entities/article.js"></script> <script src="api/models/article-model.js"></script> ...
Api / entities / article.js
Logicky som si vybral OOP implementačné prístup, tj. Pomocou entít reprezentujúcich dáta a ich ukladanie do pamäte. Začneme teda s entitou reprezentujúci článok:
'use strict'; /** * @typedef {Object} ArticleValues * @property {string} url - Unikátní URL adresa článku. * @property {string} [title] - Titulek článku. * @property {string} [content] - Text (obsah) článku. * @property {string} [description] - Krátký popis článku. */ /** * Reprezentuje datové záznamy článků v redakčním systému. * @constructor * @param {ArticleValues} values - Hodnoty záznamu článku. * @implements {ArticleValues} * @throws {Error} Jestliže hodnoty záznamu článku nejsou validní. */ function Article(values) { // Data článku. var data = { url: null, title: '', content: '', description: '' }; // Definuj data článku jako jeho vlastnosti. Object.keys(data).forEach((function (key) { Object.defineProperty(this, key, { get: function () { return data[key]; }, set: function (value) { if (typeof value !== 'string') throw new TypeError('Pole ' + key + ' musí být typu string!'); if (!value) throw new Error('Pole ' + key + ' nemůže být prázdné!'); data[key] = value; }, enumerable: true }); }).bind(this)); // Inicializace povinné URL. this.url = values.url; // Automatická inicializace dalších nepovinných hodnot. for (var key in this) if (this.hasOwnProperty(key) && values.hasOwnProperty(key)) this[key] = values[key]; }
Niekoho možno trochu prekvapí typová notácie JS dokumentácie. Používam tu JSDoc.
Api / models / article-model.js
Ďalej vytvoríme model pre správu článkov, hlavne ich ukladanie. Najskôr som si hovoril, že to urobím iba do pamäti, tzn pri obnovení stránky by sa všetko vynulovali. Potom som sa však rozhodol využiť perzistentné úložisko LocalStorage na strane klienta, keď už chceme písať tie moderné webové aplikácie. Tento model, pracujúce s lokálnym úložiskom, bude vyzerať nejako takto:
'use strict'; /** * Model poskytující metody pro správu článků v redakčním systému. * @constructor */ function ArticleModel() { /** * Vrátí seznam článků. * @returns {Article[]} - Seznam článků. */ this.getArticles = function () { var articles = []; for (var key in localStorage) if (localStorage.hasOwnProperty(key)) articles.push(this.getArticle(key)); return articles; }; /** * Vrátí článek podle jeho URL. * @param {string} url - URl článku. * @returns {null|Article} Článek s danou URL, nebo null, pokud takový neexistuje. */ this.getArticle = function (url) { return JSON.parse(localStorage.getItem(url)); }; /** * Uloží článek. Pokud již existuje článek s danou URL provede editaci. * @param {Article} article - Článek. */ this.saveArticle = function (article) { localStorage.setItem(article.url, JSON.stringify(article)); }; /** * Odstraní článek. * @param {string} url - URL článku. */ this.removeArticle = function (url) { localStorage.removeItem(url); } }
Api / api.js
Posledná časť API tvoria samotné mapovanie dotazov na vyššie
implementované funkcie pomocou knižničného modulu
ngMockE2E
:
'use strict'; /** Konfigurace simulace serverového API. */ app.run(function ($httpBackend) { // Model pro práci s články. var articleModel = new ArticleModel(); // Výchozí URL adresa simulovaného API. var apiUrl = '/api'; // Adresa simulovaného API pro správu článků. var articlesApiUrl = apiUrl + '/articles'; // Regex výraz pro dynamický parametr :url v adrese simulovaného API správy článků. var articleApiUrlRegex = new RegExp(articlesApiUrl.replace(/\//g, '\/') + '\/\\w+'); // Předem připravené výchozí články. var defaultArticles = [ new Article({ url: 'uvod', title: 'Úvod', content: '<p>Vítejte na našem webu!</p>\r\n\r\n<p>Tento web je postaven na <strong>jednoduchém redakčním systému v AngularJS frameworku</strong>.</p>', description: 'Úvodní článek na webu v AngularJS' }), new Article({ url: 'chyba', title: 'Stránka nebyla nalezena', content: '<p>Litujeme, ale požadovaná stránka nebyla nalezena. Zkontrolujte prosím URL adresu.</p>', description: 'Stránka nebyla nalezena.' }) ]; // Inicializuje výchozí články, pokud neexistují. for (var i = 0; i < defaultArticles.length; ++i) if (!articleModel.getArticle(defaultArticles[i].url)) articleModel.saveArticle(defaultArticles[i]); // Mapování URL adres API na lokální data. // GET "/api/articles" vrací seznam všech aktuálních článků. $httpBackend.whenGET(articlesApiUrl).respond(function () { return [200, articleModel.getArticles()]; }); // GET "/api/articles/:url" vrací článek s danou URL. $httpBackend.whenGET(articleApiUrlRegex).respond(function (method, url) { var article = articleModel.getArticle(url.split('/')[3]); return (article ? [200, article] : [404, {}, {}, 'Článek nenalezen!']); }); // POST "/api/articles/:url" uloží článek článek s danou URL. $httpBackend.whenPOST(articleApiUrlRegex).respond(function (method, url, data) { try { var article = new Article(angular.fromJson(data)); articleModel.saveArticle(article); return [201, article, {Location: '/' + article.url}]; } catch (error) { if (error instanceof Error) return [400, {}, {}, error.message]; console.error(error); return [500]; } }); // DELETE "/api/articles/:url" smaže článek s danou URL. $httpBackend.whenDELETE(articleApiUrlRegex).respond(function (method, url) { articleModel.removeArticle(url.split('/')[3]); return [204]; }); // POST "/api/contact-messages" zpracuje poslanou kontaktní zprávu. $httpBackend.whenPOST(apiUrl + '/contact-messages').respond(function (method, url, data) { var message = angular.fromJson(data); if (parseInt(message.year) !== (new Date()).getFullYear()) return [400, {}, {}, 'Chybně vyplněný antispam!']; alert('Odesílatel: ' + message.email + '\n' + message.content); return [201, message]; }); // Požadavky na soubory šablon naší aplikace necháme projít. $httpBackend.whenGET(/templates\//).passThrough(); });
Myslím, že kód je dobre zdokumentovaný sám o sebe a ak rozumiete HTTP, nie je na ňom nič zložitého.
Len poznámka, implementoval som tu rovno aj API pre odosielanie správ kontaktného formulára, ktorý budeme implementovať neskôr v rámci nášho kurzu. To aby sme sa k API nemuseli zbytočne vracať a upravovať ho.
To je z dnešnej lekcie všetko. Myslím, že to aj bohato stačí. V lekcii budúci, Jednoduchý redakčný systém v AngularJS - Výpis článku , sa už budeme venovať zas len AngularJS a to konkrétne kontrolerům aj šablónam. Tým náš projekt sprevádzkujeme