W poprzednim odcinku poradnika udało nam się wyciągnąć czynność pobierania danych z API do osobnej usługi. W celu samego pobrania danych użyliśmy metody get usługi $http dostępnej w Angularze. Usługa ta umożliwia wysyłanie żądań takich jak DELETE, PUT, GET czy POST, czyli w praktyce zapełnia pełny dostęp do usług typu RESTful.
Mimo wszystko posiada jednak pewne wady: deklaracja wszystkich możliwych żądań do obsługi usługi znacznie zaciemni obraz kodu, stanie się on mniej czytelny, pewne fragmenty będą się stale powtarzać, różnice w zapytaniach nie będą zbyt duże a ilość nadmiarowego kodu znaczna.
By wszystkie powyższe problemy nie miały miejsca Angular proponuje kolejną usługę o nazwie $ngResource. Usługa ta musi być dołączona oddzielnie, obok głównego skryptu Angulara:
1 2 |
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.3/angular.js"></script> <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.7/angular-resource.js"></script> |
W nowej wersji naszej aplikacji z poprzedniego wpisu, spróbujemy użyć usługi ngResource w celu pobrania danych z API, spróbujemy też poszerzyć zakres metod o pobranie pojedynczego postu. W celu pobrania informacji o pojedynczym poscie użyjemy żądania:
GET https://jsonplaceholder.typicode.com/posts/:id
gdzie :id to oczywiście id naszego wybranego postu.
Podobne czynności spróbujemy wykonać z api użytkowników:
GET https://jsonplaceholder.typicode.com/users/:id
Struktura projektu
Nasz projekt tak jak poprzednio składa się z trzech plików:
js/posts.js –kod usługi realizującej dostęp do danych
js/postsController.js –kod kontrolera
views/api.html – widok
Przyjrzyjmy się samej inicjalizacji usługi i jej użyciu:
js/posts.js:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
(function() { var dependencies = [ 'ngResource' ]; angular .module('posts', ['ngResource']) .factory('posts', ['$http','$resource', posts]) .factory('users', ['$http','$resource', users]); function posts($http,$resource) { var resource = $resource('https://jsonplaceholder.typicode.com/:object/:id', {}, { 'findall': {method: 'GET', params: {object: 'posts'},isArray:true}, 'find': {method: 'GET', params: {object: 'posts'}} }); return resource; } function users($http,$resource) { var resource = $resource('https://jsonplaceholder.typicode.com/:object/:id', {}, { 'findall': {method: 'GET', params: {object: 'users'},isArray:true}, 'find': {method: 'GET', params: {object: 'users'}} }); return resource; } })(); |
Nasz moduł, do którego dopięliśmy ngResource, składa się z dwóch usług:
posts – do obsługi modelu postów
users – do obsługi modelu użytkowników
Obie zwracają obiekt typu $resource, w którym zdefiniowaliśmy po dwa żądania:
findall – zdefiniowany jako żądanie typu GET, które za parametr object przyjmuje users (dla serwisu users, a posts dla serwisu posts) – (params: {object: ‘posts’}), a przy tym zwracające tablicę (isArray:true) wyników
find – zdefiniowany jako żądanie typu GET, które za parametr object przyjmuje users (dla serwisu users, a posts dla serwisu posts) – (params: {object: ‘posts’}), żądanie to powinno przyjmować także id szukanego postu (bądź użytkownika), ale to id będziemy przekazywać bezpośrednio z kontrolera.
Generalnie zdefiniowaliśmy więc 4 żądania:
findall GET https://jsonplaceholder.typicode.com/users
find GET https://jsonplaceholder.typicode.com/users/:id
findall GET https://jsonplaceholder.typicode.com/posts
find GET https://jsonplaceholder.typicode.com/posts/:id
Spróbujmy wywołać je z naszego kontrolera:
js/postsController.js:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
var dependencies = [ 'posts' ]; angular.module('PostsApp', dependencies) .controller('PostsController', function($http,posts,users,$scope) { var vm = this; vm.users = false; vm.posts = false; vm.postsfromapiservice = {}; vm.usersfromapiservice = {}; vm.choosenPost = {}; posts.findall({}, function(data) { vm.postsfromapiservice.items = data; }); users.findall({}, function(data) { vm.usersfromapiservice.items = data; }); vm.choose = function(id) { posts.find({id: id}, function(data) { vm.choosenPost = data; }); }; }); |
Nasz kontroler niczym nie odbiega od poprzednich, pamiętamy o wstrzyknięciu dwóch nowych serwisów, a także tworzymy dwie zmienne służące do obsługi postów i userów:
1 2 |
vm.postsfromapiservice = {}; vm.usersfromapiservice = {}; |
Tworzymy również zmienną choosenPost, która ma przechowywać dane pojedynczego, wybranego postu, którego dane pobierzemy bezpośrednio z API.
Dalsze linie kodu to użycie dwóch obiektów usług, a właściwie ich metod findall(). Pobraną listę zapisujemy do stworzonych kilka sekund temu zmiennych.
Ostatni etap naszego kontrola to obsługa funkcji zmieniającej wybrany post, widzimy, iż skutkiem działania funkcji choose() jest wysłanie poprzez nasz obiekt usługi zapytania find, oczywiście z parametrem ID pobranym z elementu, na którym wykonana została akcja. Wynik pobrania postu zapisywany jest w trzeciej zmiennej.
Przyjrzyjmy się widokowi, który wieńczy dzieło:
views/api.html:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.3/angular.js"></script> <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.7/angular-resource.js"></script> <script src="../js/postsController.js"></script> <script src="../js/posts.js"></script> <html ng-app="PostsApp"> <div ng-controller="PostsController as vm"> {{ vm.choosenPost.id}} {{vm.choosenPost.body }} <div style="width:50%;float:left"> <h1>Posts from API Service NG Resource</h1> <ul > <li ng-repeat="x in vm.postsfromapiservice.items"> <button ng-click="vm.choose(x.id)">Choose</button>{{ x.id}} | {{x.body }} </li> </ul> </div> <div style="width:50%;float:left"> <h1>Users from API Service</h1> <ul > <li ng-repeat="x in vm.usersfromapiservice.items"> {{ x.id}} | {{x.email }} </li> </ul> </div> </div> </html> |
Dołączanie plików JSa nie wymaga komentarza, nasz kod nie różni się od poprzednich widoków, jedyną większą zmianą jest dodanie przycisków, które używają dyrektywy ng-click, która to reaguje wywołaniem funkcji choose(id) w momencie wybrania jej poprzez klikniecie myszką.
1 |
<button ng-click="vm.choose(x.id)">Choose</button>{{ x.id}} | {{x.body }} |
Wybranie przycisku uruchamia funkcję choose z parametrem odpowiadającym danemu rekordowi (x.id). Funkcja ta w naszym kontrolerze uruchamia odpowiednią usługę i otrzymuje dane o pobranym obiekcie. Dane te zapisane w zmiennej choosenPost wyświetlane są następnie w pierwszej linii naszego widoku:
1 |
{{ vm.choosenPost.id}} {{vm.choosenPost.body }} |
Całość po uruchomieniu powinna wyglądać mniej więcej tak jak na obrazku poniżej:
Oczywiście usługa ngResource jest znacznie bardziej rozbudowana, niniejszy wpis jest tylko swoistym wprowadzeniem do tematu. Warto poświęcić kilka chwil, bo usługa jest niesamowicie przydatna i często się przydaje.
DEMO