10

So I have a collection of objects in the server I want to populate an ng-repeat when the page loads.

I had a made a factory which fetched the list from a resource on the server, like so:

  app.factory('objectArray', ['$http', function($http) {
      // This is returning a $$state object
      // instead of response.data...
      return $http.post('/get_collection').then(function(response) {
          console.log(response.data);
          return response.data;
      });
  }]);

I've had this code work before when using ui-router and the resolve property in the state declaration. However when injecting this factory directly into my controller, instead of getting response.data I am getting a $$state object.

My controller looks like this:

 app.controller('applicationController', ['$scope', 'objectArray', function($scope, objectArray) {
     $scope.array = objectArray;
     console.log($scope.array);
 }]);
Vivz
  • 6,625
  • 2
  • 17
  • 33
Alex Turner
  • 247
  • 1
  • 2
  • 12
  • Possible duplicate of [How do I return the response from an asynchronous call?](http://stackoverflow.com/questions/14220321/how-do-i-return-the-response-from-an-asynchronous-call); (see [this duplicate](http://stackoverflow.com/questions/22951208/return-value-after-a-promise) aswell) – Kenney Nov 21 '15 at 13:27
  • @Kenney I'm most curious about why this works in angular ui-router without any additional code, but in this case there is {$$state:object} – Alex Turner Nov 21 '15 at 13:34
  • I'm not sure, but you cannot return values from [`Promise.then`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/then). Was your approach something like [this](https://github.com/angular-ui/ui-router/issues/77)? – Kenney Nov 21 '15 at 13:41

3 Answers3

12

What the $http service returns (and thus the objectArray service is) is called a promise. You can access the actual data by registering a callback function that will be called when the data is available, i.e. when the response to the http request comes back from the server:

objectArray.then(function(data) {
    $scope.array = data;
});

The reason you directly have access to the data when using resolve is that the router, when the resolve function returns a promise, waits for the promise to be resolved. And only then, it extracts the data from the promise and injects the data into the controller.

To better understand promises, you could read the following article (and its sequel).

JB Nizet
  • 678,734
  • 91
  • 1,224
  • 1,255
3

As @JB Nizet noticed, you code is fine, just resolve it in controller Here's working demo

angular.module('app', []);
angular.module('app').factory('objectArray', ['$http', function($http) {
  // This is returning a $$state object
  // instead of response.data...
  ////// changed request type and url for the sake of example
  return $http.get('http://jsonplaceholder.typicode.com/posts/1')
.then(function(response) {
  console.log(response.data);
  return response.data;
});
}]);
angular.module('app').controller('applicationController', ['$scope', 'objectArray', function($scope, objectArray) { 
  //////resolve it here
  objectArray.then(function(successResponse){
    $scope.array = successResponse;
    console.log($scope.array);
  });
}]);
angular.bootstrap(document.body, ['app']);
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-controller="applicationController">
  <h5>Hello {{array.title}}</h5>
</div>
Medet Tleukabiluly
  • 11,662
  • 3
  • 34
  • 69
  • 1
    Returning a promise of data (through promise chaining) instead of a promise of HTTP response is actually a good thing. The OP's factory was perfectly fine. – JB Nizet Nov 21 '15 at 13:56
  • And your second example is a bad idea: now the caller has no way to know if/when the http request fails. You should also read http://blog.ninja-squad.com/2015/05/28/angularjs-promises/ – JB Nizet Nov 21 '15 at 14:00
  • @JBNizet i indeed know that in 2 example, there must be 2 callbacks, also updated answer, you were right indeed – Medet Tleukabiluly Nov 21 '15 at 14:05
  • No, there shouldn't be any callback. Returning the promise is the right way. That's what they're for. – JB Nizet Nov 21 '15 at 14:06
  • @JBNizet i don't agree, there might be conditions were you don't need to return anything from promise, just execute your callback, but still depends on usecases, in that particular example, i was sayin that 2 callbacks for success-response and error-response. And i think giving options of doing things in different ways helps people more – Medet Tleukabiluly Nov 21 '15 at 14:09
  • Using callbacks makes things harder for the caller to pass just one callback (if it's only interested in success or error), prevents the caller from chaining calls, makes the code harder to test, and prevent the caller from passing the promise to another function of to return it from other functions. Promises were precisely invented because they're much more usable than callbacks. So, I think we'll agree to disagree. – JB Nizet Nov 21 '15 at 14:15
0

errors

@ angular.js:14800 [AngularJS - Leaflet] The marker definition is not valid.
17:49:07.437 angular.js:14800 [AngularJS - Leaflet]  Received invalid data on the marker $promise.
17:49:07.438 angular.js:14800 [AngularJS - Leaflet] The marker definition is not valid.
17:49:07.439 angular.js:14800 [AngularJS - Leaflet]  Received invalid data on the marker $resolved.

$$state was the problem.

markersFactory.query().$promise.then(function(data) {
    // open_street_maps to replace google maps
    angular.extend($scope, {
        center: {
            lat:   40.7231572,
            lng:  -73.988501,
            zoom: 13,
        } ,
        markers: data,
        tiles: tilesDict.opencyclemap,
    });
});

the SOLUTION: angular.copy

markersFactory.query().$promise.then(function(data) {
    $scope.markers        = data;
    // open_street_maps to replace google maps
    angular.extend($scope, {
        center: {
            lat:   40.7231572,
            lng:  -73.988501,
            zoom: 13,
        } ,
        markers: angular.copy($scope.markers),
        tiles: tilesDict.opencyclemap,
        });
});

thumbs up!.. =)

Brian Sanchez
  • 832
  • 1
  • 13
  • 11