0

I have an API call that's working great, but I'd like to use it on several controllers so I moved it to it's own service. I'm running into what looks like a classic Scope issue or a misunderstanding of Angular's digest cycle.

'use strict';
myApp.factory('Stuff',['$http', function ($http) {
  var Stuff = {};
  Stuff.data = {};
  Stuff.api = 'http://localhost:8080/api/';
  Stuff.getStuff = function() {
    var http_stuff_config = {
      method: 'GET',
      url: Stuff.api + 'stuff/'
    };
    $http(http_stuff_config).then(function successCallback(response) {
      Stuff.data = (response.data);
      console.log(Stuff.data); // Returns populated object. 
    },function errorCallback(response) {
      console.log(response.statusText);
    });
  };
  Stuff.getStuff();
  console.log(Stuff.data); // Returns empty object.
  return Stuff;
}]);

myApp.controller('appController', ['$scope','Stuff',function($scope,Stuff) {
  $scope.stuff = Stuff;
  console.log($scope.stuff.data); // Returns empty object.
  $scope.stuff.getJobs();
  console.log($scope.stuff.data); // Returns empty object.
}]);

Here's the big clue. The essential output of above, in order is...

  1. empty object (in service after calling method)
  2. empty object (in controller before calling method)
  3. empty object (in controller after calling method)
  4. populated object (in method execution from service)
  5. populated object (in method execution from controller)

So somewhere between the scope of the getStuff() method and Angular's order of operations, I'm doing something remarkably foolish. Thank you in advance.

1
  • did you mean to capture $http data? Commented Jul 19, 2016 at 20:12

4 Answers 4

1

You need to add returns on your service, or else the promise will not be returned to the controller. It is not good practice to just store the returns in your services AND NOT return the result to the controller.

This is considered bad practice because, any time you update the data on the service everyone will need to apply $scope.$watch to the service to look for updates. This can be very expensive in large scale apps.

The best Idea is to return the data to the calling controller (if you do not need to cache it, this we can talk about later) and let the controller access it via the promise service.getthing().then(function(result){});

myApp.factory('Stuff',['$http', function ($http) {
  var Stuff = {};
  Stuff.data = {};
  Stuff.api = 'http://localhost:8080/api/';
  Stuff.getStuff = function() {
    var http_stuff_config = {
      method: 'GET',
      url: Stuff.api + 'stuff/'
    };
    return $http(http_stuff_config).then(function successCallback(response) {
      return response.data;
      console.log(Stuff.data); // Returns populated object. 
    },function errorCallback(response) {
      console.log(response.statusText);
    });
  };
  Stuff.getStuff();
  console.log(Stuff.data); // Returns empty object.
  return Stuff;
}]);




myApp.controller('appController', ['$scope','Stuff',function($scope,Stuff) {
  $scope.stuff = Stuff;
  console.log($scope.stuff.data); // Returns empty object.
  $scope.stuff.getJobs().then(function(result) {$scope.stuff = result; console.log(result);});
  console.log($scope.stuff.data); // Returns empty object.
}]);
Sign up to request clarification or add additional context in comments.

1 Comment

This is exactly what I needed. Thank you very much.
0

I recommend you not to store the result inside the service itself (Stuff.data). Just return your data in the getStuff function and let the appController's scope store the data instead.

Comments

0

remember that $scope.stuff.getJobs() is async

(meaning you can't actually call console.log($scope.stuff.data) on the next line and get the data)

Now if you had a view, with something like <span ng-bind="stuff.data.property"> you could see it work just fine because the view will update by itself when the async function is done. (this is part of angular)

Comments

0

You need to understand that when you run $http, it is making an AJAX request. therefore it will not return an result immediately.

Therefore, if you attempt to use the data coming from $scope.stuff.getJobs(); immediate after invoking this function, you are likely to get nothing.

What you should do is to have your Stuff.getJobs() return a promise, and use promise.then(your own success handler) to correctly handle the returned response.

I have cleaned up your code a little bit. The following is a running sample of your code retrieving data from Yahoo Weather API.

You can play with it on CODEPEN.

html:

<div ng-app="myApp" ng-controller="appController">
  <p>{{data}}</p>
</div>

JS:

var myApp = angular.module("myApp", []);

myApp.factory('Stuff',['$http', function ($http) {
  var Stuff = {};
  Stuff.data = {};
  //sample yahoo weather api
  Stuff.api = 'https://query.yahooapis.com/v1/public/yql?q=select%20*%20from%20weather.forecast%20where%20woeid%20in%20(select%20woeid%20from%20geo.places(1)%20where%20text%3D%22nome%2C%20ak%22)&format=json&env=store%3A%2F%2Fdatatables.org%2Falltableswithkeys';

  Stuff.getData = function() {
    var http_stuff_config = {
      method: 'GET',
      url: Stuff.api + 'stuff/'
    };
    return $http(http_stuff_config);
  };

  return Stuff;
}]);

myApp.controller('appController', ['$scope','Stuff',function($scope,Stuff) {
  $scope.data = "$http service not ran";

  var uncompletedAjaxCall = Stuff.getData();
  uncompletedAjaxCall.then(
    function(responseData){
      $scope.data = responseData;
    }, 
    function(errorMsg){}
  );

}]);

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.