AngularJS: Consumindo Serviços RESTful

A maioria das SPA’s utilizam operações CRUD, e você provavelmente deve utiliza-las. Vamos a um simples exemplo.
No Angular.js temos dois serviços para enviar e recuperar dados de uma API através do verbo HTTP: o $http e $resource.
Meh… ambos retornam uma promise então porque eu deveria escolher um ao invés do outro?
$http Service
Vamos utilizar um simples código de exemplo:app.controller('MyController', ['$scope', '$http', function($scope, $http) {
$scope.users;
var baseUrl = '/api/users';
$http.get(baseUrl).then(function(response) {
$scope.users = response.data;
}, function(err) {
console.log(err);
});
}]);
Se nosso objetivo é apenas retornar uma lista de dados, esta tudo pronto, inclusive tratando possíveis erros. Inclusive, é possível trabalhar exatamente como na jQuery://....
$http.get(baseUrl).success(function(response) {
list = response.data;
}).error(function(err) {
console.log(err);
});
//....
O Serviço $http
sem dúvidas é importante em aplicações Angular, principalmente pelo simples fato de poder encadear promises e ser absurdamente simples de utilizar.
Agora vamos pensar de uma outra forma: Imagine que você vai criar um serviço interagindo diretamente com uma API RESTful, então esse nosso serviço tera métodos utilizando $http.get
, $http.post
, $http.put
e $http.delete
certo?
Logo somos obrigados a escrever novos métodos, e isso se resume em mais código que as vezes pode ser considerado desnecessário. Porque não utilizamos isso de uma forma otimizada?
$resource Service
O Serviço $resource
nada mais é do que uma fabrica que nos permite interagir com API’s RESTful facilmente e é construido sobre o serviço $http
.
Talvez seja mais simples apenas mostrar um exemplo de código:app.factory('MyService', function() {
return $resource('/api/users/:id');
});
app.controller('MyController', ['$scope', 'MyService', function($scope, MyService) {
$scope.users;
MyService.query(function(data) {
$scope.users = data;
});
}]);
Dificil não?
Um porem é que o $resource
service diferente do $http
service não vem junto com o pacote do Angular… Porque?
Se analisarmos o simples fato que em nossa aplicação não é necessário algo como o $resource
, porque então carrega-lo?
Isso é algo interessante em SPA’s varia do framework, você não precisa utilizar tudo o que ela pode te proporcionar, apenas o que é interessante para a sua aplicação que talvez só precise de um simples $http.get
.
Em todo caso, para utilizar-mos o $resource
service precisamos adiciona-lo ao nosso projeto seja fazendo download do script ou adicionando o pacote angular-resource ao nosso projeto via Bower ou NPM.
Também precisamos injeta-lo no módulo principal da aplicação:var app = angular.module('app', ['ngResource']);
A API do $resource
service nos retorna alguns métodos como get, save, query, remove e delete.
- query(): Uma solicitação GET para o recurso
/api/users/
; - get(): Uma solicitação GET para o recurso
/api/users/:id
; - save(): Uma solicitação POST para o recurso
/api/users/
; - remove(): Uma solicitação DELETE para o recurso
/api/users/:id
; - delete(): Uma solicitação DELETE para o recurso
/api/users/:id
;
Você deve ter percebido que utilizamos :id
como parâmetro opcional que será utilizados com alguns dos verbos acima como por exemplo:var resource = $resource('/api/users/:id');
// GET /api/users/1
resource.get({id: 1}, function(data) {
$scope.user = data;
});
// DELETE /api/users/1
resource.delete({ id: id });
// POST /api/users
resource.save(data);
Simples não é?
Os métodos remove
e delete
fazem exatamente a mesma ação, porem, como nem tudo é um mar de rosas, em certas versões do IE o delete pode não funcionar por ser uma palavra reservada. Por esse motivo o remove
funciona como um alias para realizar a mesma ação.
Porem, ainda temos mais um goodie: Podemos utilizar alguns métodos que deixam nossas operações de CRUD ainda mais fáceis como $save
, $delete
e $remove
que nos retornam uma promise propriamente dita, o que facilita muito em alguns casos como neste exemplo:app.controller('MyController', ['$scope', 'MyService', function($scope, MyService) {
MyService.get({ id: 1 }, function(user) {
user.name = "John Doe";
// Faz update e nos retorna uma promise (success/error)
user.$save();
});
}]);
Tudo muito bonito, tudo muito legal, mas vamos utilizar um exemplo que realmente faça sentido. Meu amigo @wbrunom desenvolveu uma API RESTful em Node como exemplo onde o tema principal são dragões, o link para a documentação pode ser visto aqui.
Vamos analisar nossos endpoints REST:
URL | Method | Params | Result |
---|---|---|---|
/api/dragons | GET | NULL | All entries |
/api/dragons/:slug | GET | NULL | Single entry |
/api/dragons | POST | JSON String | Entry Created |
/api/dragons/:slug | PUT | JSON String | Update Status |
/api/dragons/:slug | DELETE | NULL | Delete Status |
Vamos desenvolver um serviço para consumir esta API de forma simples:<!-- index.html -->
<section ng-controller="mainController as vm">
<h1>{{ vm.title }}</h1>
<ul>
<li ng-repeat="dragon in vm.dragons track by $index">
<input ng-model="dragon.name"> <button ng-click="vm.updateDragon(dragon)">
Update
</button>
</li>
</ul>
</section>
/* app.js */
;(function(angular) {
angular.module('app', ['ngResource']);
angular.bootstrap(document.body, ['app']);
})(angular);
/* dragonsApiResource.js */
;(function(angular) {
angular.module('app').factory('dragonsService', dragonsService);
dragonsService.$inject = ['$resource'];
function dragonsService ($resource) {
var res = $resource('https://dragons-api.herokuapp.com/api/dragons/:slug');
return res;
}
})(angular);
/* mainController.js */
;(function(angular) {
angular.module('app').controller('mainController', mainController);
mainController.$inject = ['dragonsService'];
function mainController (dragonsService) {
var vm = this;
vm.title = "Dragons API"
dragonsService.query(function(dragons) {
vm.dragons = dragons;
});
}
})(angular);
Apenas para informar, quando utilizamos o angular.bootstrap
não fica mais necessário utilizar o ng-app em nossa aplicação. Assim conseguimos deixar nosso markup ainda mais limpo.
Sobre o HTML, utilizamos o ng-repeat
, o que não é novidade para ninguem, mas também foi adicionado o track by
, que é um parâmetro para otimizar o render dos elementos em loops como ng-repeat
e ng-options
.
O objetivo é atualizar os dados dragão, mas como no exemplo eu deixei apenas a informação do nome no input, referenciada no ng-model
. No método updateDragon
, disparado através do click do botão recebemos a informação do dragão relacionadas ao model, e como a entidade em si é um registro da nossa coleção dragons que veio do $resource
service, ele nos fornece de graça o método $save
, o que facilita o desenvolvimento de SPA. Certo?/* mainController.js */
/* .... */
vm.updateDragon = updateDragon;
function updateDragon (dragon) {
dragon.$save();
}
/* .... */
Feito essas modificações em nosso controller, é a hora da verdade. Vamos atualizar um registro qualquer e ver o resultado.POST https://dragons-api.herokuapp.com/api/dragons 500 (Internal Server Error)
Primeiramente precisamos entender que o $resource
não possui um método que realize PUT
nativamente. Na verdade, quando utilizamos os métodos query()
e get(:id)
, eles nos retornam uma coleção ou objeto do tipo Resource, o qual nativamente conta com alguns métodos especiais de entidade como $save()
, $delete()
e $remove()
.
O método $save()
realiza um POST
, o que funciona caso o recurso não exista na API, porém quando update, o servidor precisa checar e realizar uma espécie de create or update como existe no activerecord Rails.
Os método $delete()
e $remove()
realizam requisições do tipo DELETE
. Entretando, isso só funciona desde que nós informamos a referência em nosso recurso./* dragonsApiResource.js */
/* .... */
var res = $resource('https://dragons-api.herokuapp.com/api/dragons/:slug', { slug: '@slug' });
/* .... */
O segundo parametro é um objeto que indica o que será mapeado no endpoint a cada vez que um método especial da entidade for executado. Nesse caso foi associado que nosso :slug
estará relacionado a propriedade @slug
.
Feito isso agora nosso método $save
ira apontar para o slug caso nosso recurso possua o atributo slug, do contrário podemos criar uma nova entidade utilizando nosso dragonsService, o que ira lhe lembrar muito uma Classe
ou Model
:/* dragonsApiResource.js */
/* .... */
var dragon = new dragonsService();
dragon.name = "Meu Dragão";
dragon.$save();
/* .... */
Se funciona? Mas é claro que sim.
Foi criado um novo registro na API, o qual o slug é meu-dragao
, e isso é ótimo caso seja utilizado da maneira correta, mas ainda não resolvemos o problema do update certo?
Como citei anteriormente, não possuimos um método o qual execute PUT
, então a unica coisa que falta é o update. Para realizarmos essa operação, nós precisamos modificar nosso resource e deixa-lo mais customizável. Vejamos como:/* dragonsApiResource.js */
/* .... */
var res = $resource('https://dragons-api.herokuapp.com/api/dragons/:slug', { slug: '@slug' }, {
update : {
method: 'PUT'
}
});
/* .... */
O terceiro parametro é um objeto que nos permite adicionar quantos métodos especiais forem necessários para a aplicação utilizando o prefixo $
.
Agora que temos disponível o método $update
, vamos coloca-lo em uso:/* mainController.js */
/* .... */
vm.updateDragon = function (dragon) {
dragon.$update();
}
/* .... */
Note que o retorno da API não é o registro atualizado, nem nada do tipo. Então nesse caso para atualizarmos a view, seria interessante ter um método que realiza novamente um GET
para atualizar nossa view.
Você pode ver a minha versão com esses métodos já implementados, porem o mais interessante seria criar métodos que iriam atuar dentro do próprio recurso para a realização do CRUD junto as validações necessárias e utiliza-las. Dessa forma criamos um padrão de assinatura de recurso em toda a aplicação aonde podemos utilizar promises através do $q
e deixar nosso código mais legível e com o minimo possível de lógica nos controllers.
Existem ainda várias possibilidades para se utilizar o $resource
, mas isso sera abordado com o tempo.
Espero que tenham entendido o conceito e as diferenças entre $resource
e $http
.
Join the Conversation