To już ostatni wpis cyklu AngularJS na moim blogu. W tym materiale opowiadam o asynchronicznych zapytaniach do serwera. Dzięki którym nasza aplikacja może przetwarzać i wyświetlać dane pochodzące z zewnętrznego serwera.
Trochę teorii
AngularJS ma w sobie wbudowaną usługę $http umożliwiającą wysyłanie zapytań AJAX. Usługa ta upraszcza tworzenie zapytań kierowanych do serwera do minimum. Mamy do dyspozycji metody:
GET — $http.get(url, [config]);
POST — $http.post(url, data, [config]);
PUT — $http.put(url, data, [config]);
DELETE — $http.delete(url, [config]);
HEAD — $http.head(url, [config]);
JSONP — $http.jsonp(url, [config]);
PATCH – $http.json(url, data, [config]);
Opis wykorzystanych parametrów:
- url – adres serwera do jakiego ma zostać wysłane zapytanie,
- data – dane wysłane wraz z zapytaniem,
- config – obiekt konfiguracyjny, który wpływa na obsługę wywołania.
Właściwości obiektu konfiguracyjnego
- url – adres do którego się odwołuje zapytanie,
- header – mamy możliwość dołączenia dodatkowego nagłówka,
- params – mapa parametrów przekazywana wraz z zapytaniem, może również być przekazana dzięki url,
- cache – pozwala cachować zapytania,
- data – dane przesyłane wraz z zapytaniem.
Obiekt ten ma wiele więcej adekwatności, wszystkie możemy znaleźć w oficjalnej dokumentacji AngularJS.
Odbiór odpowiedzi
Po wysłaniu zapytania do serwera nasza aplikacja w tle oczekuje na odpowiedź. Możemy ją obsłużyć na dwa sposoby:
- Za pomocą metod success() i error(),
- Za pomocą metody then().
Różnica między tymi sposobami polega na tym, iż metoda then() przyjmuje jako argumenty dwie funkcję. Pierwsza odpowiada za pozytywną odpowiedź serwera, natomiast druga za negatywną. Metody success() i error() są automatycznie rozdzielone na te wykonane poprawnie i niepoprawnie. Metody success() oraz error() przyjmują 4 parametry:
- data – dane zwrócone przez serwer,
- status – kod statusu,
- headers – dołączone nagłówki,
- config – obiekt konfiguracyjny.
Przykładowy kod przedstawiający sposób użycia metod success() i error()
$http.get(url) .success(function (data, status, headers, config) { console.log(data); }) .error(function (data, status, headers, config) { console.log("ERROR!", data); });Przykładowy kod przedstawiający sposób użycia metody then()
$http.get(url).then( function (response) { console.log(response.data); }, function (data) { console.log("ERROR!", data); } );Oba przedstawione kody realizują to samo. W przypadku otrzymania poprawnej odpowiedzi od serwera, w konsoli wyświetlają się otrzymane dane, zaś kiedy zapytanie zakończy się niepowodzeniem wyświetlany jest napis ERROR.
Odroczenia i obietnice
Warto również wspomnieć o usłudze $q, która odpowiada za odroczenia i obietnice.
Odroczenie
To zadania, które nie zostały jeszcze ukończone, Mamy możliwość odczytania obecnego stanu. Posiadają dwie metody, których zadaniem jest zwrócenie odpowiedniego wyniku. Metody:
- resolve() – gdy zadanie wykonane jest poprawnie,
- reject() – gdy zadanie wykonane jest niepoprawnie
Obietnice
To obiekt promise, do którego możemy przypisać wykonanie operacji w zależności od statusu odpowiedzi z serwera. jeżeli jednak chcemy wykonać taką samą operacje niezależnie od odpowiedzi, do tego celu możemy wykorzystać metodę finally().
Usługa $q posiada również metodę all() służącą do łączenia obietnic, dzięki czemu nasz kod staje się jeszcze bardziej czytelny.
Więcej na temat usługi $q możemy znaleźć w oficjalnej dokumentacji AngularJS – $q
Projekt
Napiszemy prostą aplikację do zarządzania zadaniami, która będzie wykorzystywać „sztuczny” serwer REST JSONPlaceholder. W celu pokazania w jak prosty sposób możemy wykorzystać wyżej opisaną usługę $http. Celem naszej aplikacji będzie:
- Wyświetlanie wczytanych przy uruchomieniu zadań,
- Dodawanie nowych zadań,
- Edytowanie istniejących zadań,
- Usuwanie zadań.
Implementacja
Na początek tworzymy kod HTML naszej aplikacji.
<!DOCTYPE html> <html lang="pl"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>AngularJS #15</title> <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous"> <style> body { background-color: #eee; } .shadow { box-shadow: 0 .5rem 1rem rgba(0, 0, 0, .15) !important; } .taskName { padding-top: 10px; } .checkbox { text-align: center; } .operation { text-align: right; } .box-input { -webkit-border-radius: 4px; -moz-border-radius: 4px; border-radius: 4px; background-color: #fff; border: 1px solid #ddd; padding: 10px 15px; margin-bottom: 50px; } .box-input h4 { padding-bottom: 20px; border-bottom: solid 1px rgb(221, 221, 221); } </style> </head> <body> <div> <h1>ToDo List</h1> <div> <div role="alert"> Something went wrong! </div> </div> <div> <ul> <li> <div> <div> <input type="checkbox"> </div> <div> <span>Kupić banany</span> </div> <div> <button type="button">Edit</button> <button type="button">Delete</button> </div> </div> </li> </ul> <div> <div> <div> <h4>Add</h4> <h4>Edit</h4> </div> </div> <div> <div> <input type="text" placeholder="Title"> </div> <div> <button type="button">Add</button> <button type="button">Save</button> <button type="button">Close </button> </div> </div> </div> </div> </div> <script src="https://code.jquery.com/jquery-3.2.1.slim.js" integrity="sha256-tA8y0XqiwnpwmOIl3SGAcFl2RvxHjA8qp0+1uCGmRmg=" crossorigin="anonymous"></script> <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script> <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.6/angular.min.js"></script> <script src="./app.mdl.js"></script> <script src="./app.ctrl.js"></script> </body> </html>Powyżej mamy czysty kod HTML. Będzie to szablon naszej aplikacji. Jak możemy zobaczyć, mamy diva z klasą error, gdzie będzie wyświetlany komunikat o błędzie przy pobieraniu danych z serwera, a także diva z klasą tasks, gdzie mamy obsługę wyświetlania, dodawania, edytowania i usuwania zadań.
Pobieranie danych
Po skonfigurowania aplikacji AngularJS (możemy o tym przeczytać we wcześniejszych wpisach) możemy przystąpić do implementacji obsługi pobierania danych. Musimy pamiętać by przy tworzeniu kontrolera wstrzyknąć do niego usługę $http.
$scope.error = false; $http.get("https://jsonplaceholder.typicode.com/todos").then( function (res) { $scope.tasks = res.data.slice(0, 10); }, function (data) { console.error(data); $scope.error = true; } );Jak możemy zobaczyć, powyżej mamy zapytanie typu get. Jako argument podajemy adres url, a następnie wywołujemy metodę then() wraz z dwoma funkcjami. W pierwszej przypisujemy do obiektu $scope dane odesłane przez serwer, przy czym warto zauważyć, iż z otrzymanych 200 rekordów pobieramy jedynie 10. Druga funkcja zmienia wartość pola error na true, dzięki czemu zostanie wyświetlony komunikat o błędzie na stronie.
<div> <h1>ToDo List</h1> <div ng-if="error"> <div role="alert"> Something went wrong! </div> </div> <div ng-if="!error"> <ul> <li ng-repeat="task in tasks"> <div> <div> <input type="checkbox" ng-model="task.completed"> </div> <div> {{ task.title }} </div> <div> <button type="button">Edit</button> <button type="button">Delete</button> </div> </div> </li> </ul> ... </div> </div>W kodzie HTML musimy dokonać zmian, tak byśmy mogli zobaczyć wyniki pobranych danych. Na początek dodaliśmy dyrektywę ng-if do diva z klasą error oraz do diva z klasą tasks. Dzięki temu komunikat o błędzie wyświetli się jedynie wtedy kiedy odpowiedź od serwera będzie negatywna, w przeciwnym wypadku użytkownik zobaczy listę zadań.
Następnie użyliśmy ng-repeat do wyświetlenia wszystkich zadań oraz ng-model dla inputu typu checkbox.
Zaznaczanie zadań jako wykonane
Do obsługi wykonania zadania potrzebujemy funkcji checked(), która wyśle do serwera aktualizację obiektu.
$scope.checked = function (task) { $http.put("https://jsonplaceholder.typicode.com/todos/" + task.id, task).then(function (res) { }) };Funkcja ta przyjmuje jeden parametr jest nim task i wysyła do serwera zmieniony przez dyrektywę ng-model obiekt.
<input type="checkbox" ng-model="task.completed" ng-click="checked(task)">Wywołanie metody po kliknięciu w checkboxa.
Dodawanie rekordów
$scope.addTask = function () { $http.post("https://jsonplaceholder.typicode.com/todos", $scope.task).then(function (res) { $scope.tasks.push(res.data); $scope.task.title = ''; }); };Tworzymy nową funkcję addTask(), w której wysyłamy zapytanie typu post do serwera, a po otrzymaniu pozytywnej odpowiedzi dodajemy zadanie do listy i czyścimy pole task.title.
<div> <div> <div> <h4>Add</h4> <h4>Edit</h4> </div> </div> <div> <div> <input type="text" placeholder="Title" ng-model="task.title"> </div> <div> <button type="button" ng-click="addTask()">Add</button> <button type="button">Save</button> <button type="button">Close </button> </div> </div> </div>W kodzie HTML dodajemy do inputu dyrektywę ng-model, zaś do przycisku Add dyrektywę ng-click wraz z wcześniej napisaną funkcją addTask().
Edytowanie rekordów
Teraz musimy utworzyć nowe pole editMode, które będzie odpowiadało za zmianę formularza na dole aplikacji z dodawania na edytowanie danych.
$scope.closeEditMode = function () { $scope.editMode = false; $scope.task = { title: '', completed: false }; }; $scope.editTask = function (task) { $scope.editMode = true; $scope.task = { id: task.id, title: task.title, completed: task.completed }; }; $scope.updateTask = function () { $http.put("https://jsonplaceholder.typicode.com/todos/" + $scope.task.id, $scope.task).then(function (res) { for (var i = 0; i < $scope.tasks.length; i++) { if ($scope.tasks[i].id === res.data.id) { $scope.tasks[i] = res.data; } } $scope.editMode = false; $scope.task = { title: '', completed: false }; }) };Teraz musimy dodać 3 funkcję. Pierwsza z nich closeEditMode odpowiada za zmianę pola editMode z true na false oraz wyczyszczenie pola task.title.
Zadaniem drugiej funkcji jest ustawienie editMode na true oraz przypisanie wybranego zadania do pola task.
Ostatnia funkcja wywołuje zapytanie do serwera metodą put. Po otrzymaniu poprawnej odpowiedzi przeszukuje listę zadań i uaktualnia zadanie na liście. Następnie ustawia pole editMode na false oraz czyści pole taks.title.
<div> <div> <div> <h4 ng-if="!editMode">Add</h4> <h4 ng-if="editMode">Edit</h4> </div> </div> <div> <div> <input type="text" placeholder="Title" ng-model="task.title"> </div> <div> <button type="button" ng-if="!editMode" ng-click="addTask()">Add</button> <button type="button" ng-if="editMode" ng-click="updateTask()">Save</button> <button type="button" ng-if="editMode" ng-click="closeEditMode()">Close </button> </div> </div> </div>W kodzie HTML dodajemy dyrektywę ng-if do znaczników h4, dzięki czemu po kliknięciu przycisku Edit zmienia się nam nazwa formularza z Add na Edit. Dodatkowo dodajemy ng-if oraz dyrektywę ng-click wraz z odpowiednimi metodami do przycisków.
Usuwanie rekordów
Ostatnią rzeczą jaka została nam do zrobienia jest usuwanie rekordów.
$scope.deleteTask = function (taskId) { var confirmDelete = confirm("Are you sure you want to delete?"); if (confirmDelete) { $http.delete("https://jsonplaceholder.typicode.com/todos/" + taskId).then(function (res) { for (var i = 0; i < $scope.tasks.length; i++) { if ($scope.tasks[i].id === taskId) { $scope.tasks.splice($scope.tasks.indexOf($scope.tasks[i]), 1); } } }); return true; } else { console.log('No'); return false; } };Tworzymy funkcję deleteTask, gdzie przekazujemy id określonego zadania. Po kliknięciu w przycisk Delete zostaje wyświetlony alert z dwoma przyciskami Ok i Anuluj. Wybierając Ok warunek jest spełniony i wykonuję się zapytanie do serwera metodą delete. Gdy otrzymamy poprawną odpowiedź, przeszukujemy listę zadań i usuwamy dane zadanie. Klikając przycisk Anuluj zostanie wyświetlony w konsoli napis No.
Działanie aplikacji
Screen ten przedstawia aplikację po załadowaniu danych.
Na screenie widzimy aplikację po otrzymaniu negatywnej odpowiedzi od serwera przy próbie pobrania danych.
Widok aplikacji podczas edytowania zadania.
Widok podczas usuwania zadania.
Podsumowanie
W ostatnim wpisie tego cyklu przedstawiłem w jaki sposób możemy komunikować się z serwerem dzięki zapytań AJAXowych. Jak mogliśmy zobaczyć w utworzonej przez nas aplikacji użycie usługi $http jest bardzo proste. Technologia ta pozwala nam na pobieranie danych z zewnętrznych serwerów oraz ich zapisywanie, modyfikowanie i usuwanie. Dzięki tej usłudze pisana przez nas aplikacja staje się w pełni użytkowa.