【问题标题】:How to asynchronously populate a $scope variable in AngularJS?如何在 AngularJS 中异步填充 $scope 变量?
【发布时间】:2015-07-23 13:39:25
【问题描述】:

我有以下服务:

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);
        });
    };
}]);

以及以下主控制器代码:

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();

}]);

问题是,当视图请求$scope.fonts的内容时,它仍然是空的。

加载结束后如何更新$scope.fonts和$scope.families?

我可以使用$scope.$watch,但我确信有一种更简洁的方法可以做到这一点......

【问题讨论】:

    标签: javascript angularjs asynchronous angularjs-scope angularjs-service


    【解决方案1】:

    这确实是 promises 的用途。您的服务应该返回一个要解决的承诺。您还可以简化您的服务:

    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;
        };
    }]);
    

    然后在你的控制器中,使用 promises 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;
        });
    }]);
    

    由于 $http,这是未经测试的,但这里有一个使用 $timeout 的演示:

    JSFiddle

    【讨论】:

    • $http 已经是一个 Promise 实现,无需将其包装在另一个内。
    • 这是事实,并且是一个很好的免责声明。 OP 正在存储并返回结果以供将来调用。可以为缓存提出一个论据,但目的是展示 Promise 的有用性。这当然可以使用 return $q.when(self.families); 进行优化,但上面的解决方案更好地展示了如何使用 Promise。
    【解决方案2】:

    考虑传递一个回调函数。

    服务:

    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);
        });
    };
    

    控制器:

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

    这可以稍微整理一下,因为回调消除了对其中一些代码的需要,但它将作为示例。

    【讨论】:

    • 这是一种方法,但是如果请求超时,脚本不会卡住吗?
    • 不,就执行而言,它会在 HTTP 调用处停止并返回到任何调用它的地方。然后,如果数据返回,则触发回调函数 - 如果没有,则不会造成任何伤害。
    【解决方案3】:

    感谢所有答案!我最终混合使用了回调和承诺,如下所示:

    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);
            });
        };
    });
    

    并且,在控制器中:

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

    我尝试了您的所有建议,但这是与我的其余代码一起使用的最佳建议。

    【讨论】:

      【解决方案4】:

      在您的this.getFonts 函数(和您的其他函数)中,您从this 调用数据,它指向函数而不是您想要的控制器范围。请尝试以下方法:

      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
      };
      

      【讨论】:

        【解决方案5】:

        我会尝试将您正在调用的 getScope 和 getFonts 主体包装在

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

        【讨论】:

          【解决方案6】:

          确保在任何函数之外声明self = this 外部。

          将调用分配给要存储数据的值,然后返回。

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

          【讨论】:

            【解决方案7】:

            由于您使用的是 ui-router(我看到了 $state)。你可以在你的状态下使用 resolve 并返回一个 Promise。

            文档:https://github.com/angular-ui/ui-router/wiki

            示例:

            $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;
                  }
            }
            

            在你的情况下,你需要把你的 this.getFonts 和 getFamilies 变成 Promise

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

            有很多方法可以做到这一点,但我认为解决方法是最好的。

            【讨论】:

            • 谢谢。它运行良好,但不适合我的项目架构和我的其余代码。
            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多