1

Somewhat new to Angular and javascript. I have the following controller written use a factory service to access a local JSON file. Most of this code is derived (or completely taken) from this post by Dan Wahlin. I am unable to access the $scope.books variable outside of the function and cannot figure out why. The console.log inside the function gives me the object I am looking for, but the one outside returns undefined. What am I doing wrong here? Thanks.

app.controller('FormController', ['$scope', 'tocFactory', function ($scope, tocFactory) {

  $scope.books;

  getBooks();

      function getBooks() {
        tocFactory.getBooks().
          success(function(data, status, headers, config) {
            $scope.books = data;
            console.log($scope.books);
          }).
          error(function(data, status, headers, config) {
            // log error
     })
  }
  console.log($scope.books);
  }]);
Pankaj Parkar
  • 134,766
  • 23
  • 234
  • 299
GMarsh
  • 2,401
  • 3
  • 13
  • 22
  • 1
    Everything is OK, console.log inside `success` returns proper object. Welcome to world of asynchronus methods. – Krzysztof Safjanowski Aug 18 '15 at 19:31
  • I guess that depends on your definition of OK. I cannot bind to it in my HTML or use it in other parts of my controller in its current form. How can I make it so that I can? – GMarsh Aug 18 '15 at 19:35
  • The whole point of data-binding for UI is that whenever your data is changed, the UI reflects that automatically. Your bindings must not be right. – David Zech Aug 18 '15 at 19:36

3 Answers3

3

Because you are making an ajax and then executing the next line of code. Which doesn't mean you have guarantee that your ajax response is ready on execution of next line of code.

You could always gets its response value inside the success function of $http which gets called when ajax call executed successfully.

Read here How asynchronous call works?

Community
  • 1
  • 1
Pankaj Parkar
  • 134,766
  • 23
  • 234
  • 299
2

actually this isn't an issue of scope, but timing. The getBooks function is executed asynchronously, so when your console log happens it most likely will not have been bound to anything. You could test this easily with interval to see how this is happening:

app.controller('FormController', ['$scope', 'tocFactory', function($scope, tocFactory) {
  $scope.books;
  getBooks();
  function getBooks() {
    tocFactory.getBooks()
      .success(function(data, status, headers, config) {
        $scope.books = data;
        console.log($scope.books);
      })
      .error(function(data, status, headers, config) {
        // log error
      })
  }
  setInterval(function(){
    console.log($scope.books);
  }, 1000);
}]);
wesww
  • 2,863
  • 18
  • 16
  • what if response will take longer than 1 second? – Pankaj Parkar Aug 18 '15 at 19:36
  • 1
    my quick demo code is suggesting playing with setInterval, not setTimeout. It will just repeat logging it every 1 second forever, so most likely it would out put `undefined` once or more and then output some value after .success is called – wesww Aug 18 '15 at 19:37
  • haha no worries, I figured. Your answer is an equally good explanation of the problem, I have upvoted it – wesww Aug 18 '15 at 19:40
  • I did yours too.. Cheers :) – Pankaj Parkar Aug 18 '15 at 19:41
0

You can use $q service to handle asynchronous code with promises :

app.controller('FormController', ['$scope', '$q', 'tocFactory', function ($scope,  $q, tocFactory)
{

    var getBooks = function()
    {
        var deferred = $q.defer();

        tocFactory.getBooks().
        success( function(data, status, headers, config)
        {
            $scope.books = data;
            deferred.resolve();
        } ).
        error( function(data, status, headers, config)
        {
            deferred.reject();
        } );

        return deferred.promise;
    };

    getBooks().then( function(res)
    {
        console.log($scope.books); // success : your data
    }, function(res)
    {
        console.log($scope.books); // error : undefined
    } );

    console.log($scope.books); // undefined

} ] );

I haven't tested this code but it should work and show you promises principle. More about $q service : https://docs.angularjs.org/api/ng/service/$q

Ziad
  • 419
  • 3
  • 7
  • 15
  • 1
    There is no need to use `$q` as `$http` already returns a promise. Even more – success and error methods are now deprecated – https://docs.angularjs.org/api/ng/service/$http – The $http legacy promise methods success and error have been deprecated. Use the standard then method instead. If $httpProvider.useLegacyPromiseExtensions is set to false then these methods will throw $http/legacy error. – Krzysztof Safjanowski Aug 18 '15 at 20:27