user2889669
user2889669

Reputation: 41

Using $http and $q to fetch data

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

Answers (3)

jessegavin
jessegavin

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

Nikola Yovchev
Nikola Yovchev

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

Nikos Paraskevopoulos
Nikos Paraskevopoulos

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

Related Questions