AngularJS : controller scope won't sync with promise
C

2

6

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
        }
    ];
    });
Christoperchristoph answered 7/5, 2014 at 14:32 Comment(10)
So your question is, why console.log("data", $scope.data); // => data null? If yes, then it's probably because log executes before the async call finishes. Sidenote the two calls to MyService.all(); will actually trigger two requests. I think the first is not necessary.Abducent
Yes, exactly! Thanks for pointing this out, wasn't very clearChristoperchristoph
I'd start off by getting it working inside your controller, so on success do $scope.data = data; When you can see the call working succesfully then start refatoring with services, think you are adding complexity. $http already returns a promise, so you don't need to use another promise. docs.angularjs.org/api/ng/service/$httpPatrilineage
@mykepwnage data being null for some time should not be a problem at all. If it is, then you might want to add how you're using data to your question.Abducent
@LightningShield fair enough. I did try that earlier, but got the same result so I figured it was safe to just keep it in the service. I've added some code showing it still doesn't work when I keep it in the controller.Christoperchristoph
@Abducent True! I've added how the data needs to be used.Christoperchristoph
@mykepwnage As you're dealing with async stuff directly using angular.forEach($scope.data, will not work. Do this in side the success callback or a function which will be called from inside the success callback.Abducent
@Abducent That's interesting, and I don't quite understand it. So I moved my angular.forEach inside the success callback, and then output the data changes to the console, and they were there. However, they view is not seeing this. Or, maybe the view is being rendered before the data can be saved to the scope...? I'll update to show.Christoperchristoph
@Abducent okay please see updated service/controller. I changed to the format in your answer below.Christoperchristoph
@Abducent okay there we go, I just had to put all the $scope calls inside the success callback as well! Thank you so much!!!Christoperchristoph
A
5

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.

Abducent answered 7/5, 2014 at 14:58 Comment(0)
I
0

I'm facing the same issue. I solve that issue by using $rootScope inside the promise rather than the $scope.So $rootScope.data will be avaiable in console.log

Inartistic answered 3/12, 2015 at 9:38 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.