How to populate $ scope variable asynchronously in AngularJS?

I have the following service:

app.service('Library', ['$http', function($http) {

    this.fonts = [];
    this.families = [];

    // ... some common CRUD functions here ...

    // Returns the font list
    this.getFonts = function() {
        if(_.isEmpty(this.fonts)) this.updateFonts();
        return this.fonts;
    };

    // Returns the family list
    this.getFamilies = function() {
        if(_.isEmpty(this.families)) this.updateFamilies();
        return this.families;
    };

    // Update the font list
    this.updateFonts = function() {
        var self = this;
        $http.get(BACKEND_URL+'/fonts').success(function(data) {
            self.fonts = data;
            console.log('Library:: fonts updated', self.fonts);
        });
    };

    // Update the family
    this.updateFamilies = function() {
        var self = this;
        $http.get(BACKEND_URL+'/families').success(function(data) {
            var sorted = _.sortBy(data, function(item) { return item });
            self.families = sorted;
            console.log('Library:: families updated', self.families);
        });
    };
}]);

      

And the following main controller code:

app.controller('MainController', ['$scope', '$state', 'Cart', 'Library', function($scope, $state, Cart, Library) {
    console.log('-> MainController');

    // Serve the right font list depending on the page
    $scope.fonts = $state.is('home.cart') ? Cart.getFonts() : Library.getFonts();
    $scope.families = Library.getFamilies();

}]);

      

The problem is that when the view asks for content $scope.fonts

, it is still empty.

How to update $scope.fonts

and $scope.families

when the download is over?

I could use $scope.$watch

, but I'm sure there is a cleaner way to do this ...

+3


source to share


7 replies


Thanks for answers! I ended up using a combination of callback and promise as follows:

app.service('Library', function($http) {

    // Returns the font list
    this.getFonts = function(callback) {
        if(_.isEmpty(self.fonts)) return self.updateFonts(callback);
        else return callback(self.fonts);
    };

    // Update the font list
    this.updateFonts = function(callback) {
        return $http.get(BACKEND_URL+'/fonts').success(function(data) {
            self.fonts = data;
            callback(data);
        });
    };
});

      

And in the controller:



app.controller('MainController', function(Library) {
    Library.getFonts(function(fonts) { $scope.fonts = fonts });
});

      

I've tried all your suggestions, but this is the best one that works with the rest of my code.

+1


source


These are actually promises . Your service needs to return a promise that needs to be resolved. You can also simplify your maintenance:

app.service('Library', ['$http', '$q', function($http, $q) {
    var self = this;

    self.families = [];

    // Returns the family list
    self.getFamilies = function() {
        var deferred = $q.defer();

        if(_.isEmpty(self.families)) {
            $http.get(BACKEND_URL+'/families').success(function(data) {
                var sorted = _.sortBy(data, function(item) { return item });
                self.families = sorted;
                deferred.resolve(self.families);
                console.log('Library:: families updated', self.families);
            });
        } else {
            deferred.resolve(self.families);
        }

        return deferred.promise;
    };
}]);

      

And then in your controller use the promises method then

:



app.controller('MainController', ['$scope', '$state', 'Cart', 'Library', function($scope, $state, Cart, Library) {
    console.log('-> MainController');

    // Serve the right font list depending on the page
    $scope.fonts = $state.is('home.cart') ? Cart.getFonts() : Library.getFonts();
    Library.getFamilies().then(function(result) {
        $scope.families = result;
    });
}]);

      

This is untested due to $ http, but here is a demo using $ timeout:

JSFiddle

+2


source


Consider passing a callback function.

Services:

this.getFonts = function(callback) {
    if(_.isEmpty(this.fonts)) this.updateFonts(callback);
    return this.fonts;
};

this.updateFonts = function(callback) {
    var self = this;
    $http.get(BACKEND_URL+'/fonts').success(function(data) {
        self.fonts = data;
        console.log('Library:: fonts updated', self.fonts);
        callback(data);
    });
};

      

Controller:

Library.getFonts(function (data) { $scope.fonts = data; });

      

This can be a bit tweaked as the callback removes the need for some of this code, but it will serve as an example.

+1


source


In your function this.getFonts

(and other functions), you are calling data from this

that points to the function instead of the required controller scope. Try this instead:

var self = this;

self.fonts = [];
self.families = [];

// ... some common CRUD functions here ...

// Returns the font list
self.getFonts = function() {
    if(_.isEmpty(self.fonts)) self.updateFonts();
    return self.fonts; // <-- self.fonts will point to the fonts you want
};

      

0


source


I would try wrapping your getScope and getFonts bodies that you call in

$scope.$apply(function(){ ...body here... });

      

0


source


Make sure you declare self = this

outside any functions.

Assign the call to the value where you want to store the data and then return it.

var self = this;
self.data = [];

this.updateFonts = function() {

    self.fonts = $http.get(BACKEND_URL+'/fonts').success(function(data) {
        return data.data
    });
    return self.fonts
};

      

0


source


Since you are using ui-router (I saw the $ state). You can use the solution in your state and return the promise.

Doc: https://github.com/angular-ui/ui-router/wiki

Example:

$stateProvider.state('myState', {
      resolve:{
         // Example using function with returned promise.
         // This is the typical use case of resolve.
         // You need to inject any services that you are
         // using, e.g. $http in this example
         promiseObj:  function($http){
            // $http returns a promise for the url data
            return $http({method: 'GET', url: '/someUrl'});
         }
      },   
       controller: function($scope,promiseObj){
          // You can be sure that promiseObj is ready to use!
          $scope.items = promiseObj.data;
      }
}

      

In your case, you need to include your.getFonts and getFamilies in promises

this.getFonts = function(){
  return $http.get(BACKEND_URL+'/fonts').success(function(data) {
            self.fonts = data;
            console.log('Library:: fonts updated', self.fonts);
        });
}

      

There are many ways to do this, but in my opinion the solution is the best.

0


source







All Articles