Você está na página 1de 4

AngularJS: Creating A Service With $http

So I did a talk on AngularJS last night at the Pittsburgh .NET Users' Group. It was a great talk and you
guys asked a lot of great questions. One of my friends that attended suggest people use my blog as a
resource for Angular, and while I do have quite a few posts about Angular, I don't feel like it's quite as
flushed out in that department as I'd like it to be before I wanted my friends plugging it like that. So I've
decided to try to fill that gap, at least as far as Angular goes, over the next few weeks, if I can. (Don't
worry, PGHNUG attendees, I'll still try to get a well commented Angular/Web API solution up on GitHub
soon).

HTTP Service Calls In Angular


One of the most common questions I see on StackOverflow regarding Angular, are questions involving the
creation of AJAX-based angular services using $http. Commonly the pattern used is very reminiscent of
JQuery, where there's a method with a callback when the data is received.

Common Callback Example


app.factory('myService', function($http) {
return {
getFooOldSchool: function(callback) {
$http.get('foo.json').success(callback);
}
}
});
app.controller('MainCtrl', function($scope, myService) {
myService.getFooOldSchool(function(data) {
$scope.foo = data;
});
});

That's fine. It's an easy to understand pattern that is predictable for most other developers using your
service and most importantly, it works.

Angular Loves Promises


In Angular, there is a service called $q. It is a deferred/promise implementation built off of Q by
Kristopher Kowal. I know I've talked about deferment and promises in JavaScriptin the past, but as a very
quick refresher, the idea behind this pattern is basically to have a mechanism to signal when one (or
sometimes many) asynchronous actions are complete. It's the hub of JQuery's AJAX implementation,
Angular $http implementation and Angular's $resource implementation. I like this mechanism so much I've
even implemented it in my .NET Event Loop Framework.

EDIT: As of 1.2.0, promises are no longer resolved by templates.


So in code, that means if you return a promise from your service, and put it directly in a scope property, it
will asynchronously update that scope property and process the changes.
Since $http methods like get() and post() return promises, we can use that promise's then() method (which
also returns a promise) to pull the data out of the result. The return value from the then() method's callback
is used to resolve that promise.

Simplified Promise Pattern Example

app.factory('myService', function($http) {
return {
getFoo: function() {
//since $http.get returns a promise,
//and promise.then() also returns a promise
//that resolves to whatever value is returned in it's
//callback argument, we can return that.
return $http.get('foo.json').then(function(result) {
return result.data;
});
}
}
});
app.controller('MainCtrl', function($scope, myService) {
//DEPRECATED: The commented line below WILL NO LONGER WORK in 1.2.0
//since promises are no longer resolved by templates.
//$scope.foo = myService.getFoo();
//make the call to getFoo and handle the promise returned;
myService.getFoo().then(function(data) {
//this will execute when the
//AJAX call completes.
$scope.foo2 = data;
console.log(data);
});
};

And because some of you like to play around, here's a bit of code on plunker showing the promise pattern
for $http calls in Angular:

Edit: Complex $http calls from within a Service

Because I've been asked by friends what to do in situations where you might have nested or simultaneous
async calls in a controller, I think this blog entry is a really good place to show some examples of that,
since it falls under the same domain, so to speak.
There are of course scenarios where you might have a service method that requires more than one $http
call (or other async call) to be made before you want the service to return. This is where you'd want to use
the $q service mentioned above.

Example of dealing with multiple async calls to return simultaneously


app.factory('myService', function ($http, $q){
return {
getItems: function (){
//$q.all will wait for an array of promises to resolve,
// then will resolve it's own promise (which it returns)
// with an array of results in the same order.
return $q.all([
$http.get('items_part_1.json'),
$http.get('items_part_2.json')
])

//process all of the results from the two promises


// above, and join them together into a single result.
// since then() returns a promise that resolves ot the
// return value of it's callback, this is all we need
// to return from our service method.
.then(function(results) {
var data = [];
angular.forEach(results, function(result) {
data = data.concat(result.data);
});
return data;
});
}
};
});

Example of dealing with nested async calls in a single service call


Technically, we can deal with nested async calls without using $q. ("nested" to say that each call to $http
triggers a subsequent call to $http in order to build out some data) This simplifies the code a little, but in
my opinion makes it harder to follow. For example:
getNestedData: function (){
// get the parents.
return $http.get('parents.json')
.then(function(result) {
//return a promise to that data.
return result.data;
})
//handle the promise to the parent data
.then(function(parents) {
//get the children.
return $http.get('children.json')
//handle the promise to the children.
.then(function(result) {
//add children to the appropriate parent(s).
var children = result.data;
angular.forEach(parents, function(parent) {
parent.children = [];
angular.forEach(children, function(child) {
if(parent.childIds.indexOf(child.id) >= 0) {
parent.children.push(child);
}
});
});
//return the parents
return parents;
});
});
}

Example of nested calls using $q directly


This is a point where it really doesn't matter which route you go when it comes to what's going to work.
However, it is my opinion that using $q directly in complicated async calls or nested async calls, enhances
readability. This is simply because it becomes easier to see which promise was returned, when it was

created, and where it was resolved:

getNestedDataBetter: function (){


//create your deferred promise.
var deferred = $q.defer();
//do your thing.
$http.get('parents.json')
.then(function(result){
var parents = result.data;
$http.get('children.json')
.then(function(result) {
var children = result.data;
angular.forEach(parents, function(parent) {
parent.children = [];
angular.forEach(children, function(child) {
if(parent.childIds.indexOf(child.id) >= 0) {
parent.children.push(child);
}
});
});
//at whatever point in your code, you feel your
// code has loaded all necessary data and/or
// resolve your promise.
deferred.resolve(parents);
});
});
//return your promise to the user.
return deferred.promise;
}

Você também pode gostar