AngularJS : controller scope won't sync with promise

mykepwnage picture mykepwnage · May 7, 2014 · Viewed 8.2k times · Source

I've picked up a project and I'm trying to return some data from a service to my controller. I've been at this for about 12 hours, and have tried different methods. They all usually result in this same kind of 'missing data'.

I've tried

  • using $resource instead of $http
  • put the $http.get right inside the controller without using promises
  • the service as an actual service (instead of a factory) without a return
  • building the factory a bunch of different ways to return data in a variety of formats
  • using setTimeout and $apply

I feel what I have now is a simple as I can get it, and everything I've read says this should work

angularjs-load-data-from-service

angular-controller-cant-get-data-from-service

angularjs-promises-not-firing-when-returned-from-a-service

angularjs-promise-not-resolving-properly

angularjs-promise

These links were just from today.

I thought there might be a $scope issue, as in the past I've seen $scopes not get data when multiple controllers get used, however the site simply declares a controller in index.html as <body ng-app="myApp" ng-controller="BodyCtrl"> setup. The main app.js uses states (from ui-router I believe) like so...

.state('app.view', {
    url: '/view',
    templateUrl: 'views/view.tpl.html',
    controller: 'MyCtrl'
   })

to attach controllers to the different pages. Also, the site has a few other controllers getting data from services, which I looked through first as a template, however, the data and returns are much more complicated there than what I'm trying to get at. Bottom line though, the controllers are accessing data provided from services. They're all using $resource, which is how I started with this issue. I've stuck with $http because it should work, and I'd like to get this working with that before I move onto something 'higher level'. Also, I only need to GET from the endpoints, so felt $resource was overkill.

service

.factory('MyService', ['$http', '$q', '$rootScope', function ($http, $q, $rootScope) {
    var defer = $q.defer();
    var factory = {};
    factory.all = function () {
        $http({method: 'GET', url: 'http://URLtoJSONEndpoint'}).
            success(function (data, status, headers, config) {
                console.log('data success', data);
                defer.resolve(data);
            }).
            error(function (data, status, headers, config) {
                console.log('data error');
                defer.reject(data);
            });

        return defer.promise;
    };
    return factory;
}]);

controller

.controller('MyCtrl', ['$scope', '$state', '$stateParams', '$rootScope', 'MyService', '$q', function ($scope, $state, $stateParams, $rootScope, MyService, $q) { 
...
...
$scope.data = null;
$scope.object = MyService;
$scope.promise = MyService.all();

MyService.all().then(function (data) {
    $scope.data = data;
    console.log("data in promise", $scope.data);
})
console.log("data", $scope.data);
console.log("object", $scope.object);
console.log("promise", $scope.promise);

$http({method: 'GET', url: 'http://URL'}).
        success(function (data, status, headers, config) {
            $scope.data = data;
            console.log('data success in ctrl', data);
        }).
        error(function (data, status, headers, config) {
            console.log('data error');
        });

    console.log("data after ctrl", $scope.data);


    angular.forEach($scope.data, function (item) {
        // stuff that I need $scope.data for
    }

console log

So this is my console log, and I can get at so much, just not the actual data! Which is what I need. I even got crazy and extended my .then(function (data) { to capture all the functions in the controller which need $scope.data. That was a train wreck.

***data null***
object Object {all: function}
promise Object {then: function, catch: function, finally: function}
***data success after ctrl null***
data success in ctrl Array[143]
data success Array[143]
data in promise Array[143]
data success Array[143]

As far as I can tell, this should work, but I'm not sure where else the problem can be! Maybe I don't understand how promises work or resolve. I've used Angular before with another project, but I was there at it's start and understood how it was put together. This project was structured differently and feels much more chaotic. I'd like to simplify it, but I can't even seem to get some simple data to return!

I appreciate any help/feedback you can offer in identifying why this isn't working, thank you!

EDIT: So the question is, why is console.log("data", $scope.data) coming back null/before the promise?

A bit further down in the controller I have this

angular.forEach($scope.data, function (item) {
// stuff
}

and it doesn't seem to have access to the data.

EDIT2: I've added the $http.get I was using inside the controller, along with console logs for it and the actually forEach I need $scope.data for

EDIT3:

updated service

.service('MyService', ['$http', function ($http) {
    function all() {
        return $http({
            url: 'http://URL',
            method: 'GET'
        });
    }

    return {
        all: all
    }
}]);

updated controller

MyService.all().success(function (data) {
        $scope.data = data;

        angular.forEach($scope.data, function (item) {

            // Turn date value into timestamp, which is needed by NVD3 for mapping dates
            var visitDate = new Date(item.testDate).getTime();

            switch (item.Class) {
                case "TEST":
                    testData.push(
                        [
                            visitDate,
                            item.Total
                        ]
                    );

            }

        });

        console.log("test Data in success", testData);
    });


$scope.testData = [
        {
            "key": "Test",
            "values": testData
        }
    ];

So $scope.testData needs to be used in the view (nvd3 chart), and it's not getting the data.

SOLUTION

MyService.all().success(function (data) {
        $scope.data = data;

        angular.forEach($scope.data, function (item) {

            // Turn date value into timestamp, which is needed by NVD3 for mapping dates
            var visitDate = new Date(item.testDate).getTime();

            switch (item.Class) {
                case "TEST":
                    testData.push(
                        [
                            visitDate,
                            item.Total
                        ]
                    );
            }

        });

        console.log("test Data in success", testData);

        $scope.testData = [
        {
            "key": "Test",
            "values": testData
        }
    ];
    });

Answer

Yoshi picture Yoshi · May 7, 2014

This could be a very simple example of how your service could look like:

app.service('MyService', ['$http', function ($http) {
  function all() {
    return $http({
      url: 'data.json',
      method: 'GET'
    });
  }

  return {
    all: all
  }
}]);

In the controller you use it like so:

app.controller('AppCtrl', ['$scope', 'MyService', function ($scope, MyService) {
  $scope.data = null;

  MyService.all().success(function (data) {
    $scope.data = data;

    // init further handling of `.data` here.
  });
}]);

Demo: http://plnkr.co/edit/l8pF8Uaa312A8kPtaCNM?p=preview


To answer the question why the console logs data null: This is simply because the log happens before the async call finishes.