Reputation: 41
I am trying to use $q and $http to get data from server and wait until data is retrieved. After data is retrieved i would want to manipulate it.
This is my service with method which fetches data from server:
application
.factory('service', function ($http,$q) {
return {
fetch: function(url) {
var deferred = $q.defer();
$http.get(url).success(function(data){
deferred.resolve(data);
}).error(function(){
deferred.reject("An error occured while fetching items");
});
return deferred.promise;
}
};
});
Controller that consumes data looks like this:
service.fetch(url).then(function(data){
$scope.data = data;
},
function(errorMessage){
$scope.error=errorMessage;
});
$scope.data = ...manipulate data
Problem is that angular manipulates data before it is fetched. Any suggestions how to solve this would be much appreciated.
Upvotes: 2
Views: 3380
Reputation: 75660
If you ever need to manipulate data from a service call, a good option is to use the transformResponse option in the $http
service. It basically allows you to completely mess with the response before resolving the promise.
See this video for a walkthrough. https://egghead.io/lessons/angularjs-transformresponse
Here's an example transform function.
app.factory('searchResponseTransformer', function () {
return function (data) {
data = JSON.parse(data);
if (data.length) {
// Do data manipulation here
}
return data;
};
});
Then you can use this transform function from within your service.
app.factory('searchService', function ($http, searchResponseTransformer) {
return {
search: function(searchTerm) {
return $http({
method: "GET",
url: "/search",
params: { q: searchTerm },
transformResponse: searchResponseTransformer
});
}
}
});
Then you can use this service from your controller.
app.controller('searchCtrl', function ($scope, searchService) {
$scope.searchTerm = "";
$scope.searchResults = [];
$scope.search = function() {
searchService
.search($scope.searchTerm)
.success(function(data) {
$scope.searchResults = data;
});
}
});
And your view would look something like this..
<div ng-controller="searchCtrl">
<p><input type="text" ng-model="searchTerm" ></p>
<ul>
<li ng-repeat="item in searchResults">{{item.description}}</li>
</ul>
</div>
Upvotes: 4
Reputation: 10246
You shouldn't manipulate the data before it is fetched. Promises return immediately, so your manipulation would be overriden by the then callback. What you should do is manipulate the data in the then callback:
service.fetch(url).then(function(data){
$scope.data = ...manipulate data
},
function(errorMessage){
$scope.error=errorMessage;
});
However, to improve on this code as it is not very readable, you can create a service method to do that for you. As a replacement of $http I suggest you use Restangular. It is slighty more object oriented. Check this blog post I wrote:
http://ath3nd.wordpress.com/2013/08/05/15/
Restangular, just like $http, has the notion of promises, so you can do:
function fetch(url){
var deferred = $q.defer();
Restagular.one(url).get({})
.then(function(result){
//do your manipulation, e.g.
result.newlyCreatedProp = 'newlyCreatedProp';
deferred.resolve(result);
});
return deferred.promise;
}
And then you can do that in your controller:
service.fetch().then(function(data){
$scope.data = data;
});
Which would work immediately since the promise is returned immediately.
Upvotes: 1
Reputation: 40298
The problem is that $scope.data = ...
runs just after service.fetch(url)
is called not after it returns. The AJAX call service.fetch(url)
returns with data when the success callback is called, so the correct code is:
service.fetch(url).then(
function(data) {
$scope.data = data; // keeping it for anyone else that needs it, e.g. the view
$scope.data = ... // manipulate data HERE
},
function(errorMessage) {
...
}
);
Upvotes: 2