如何在 AngularJS 中异步填充 $scope 变量?
How to asynchronously populate a $scope variable in AngularJS?
我有以下服务:
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
,但我确信有一种更简洁的方法...
考虑传递回调函数。
服务:
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; });
这可以稍微整理一下,因为回调消除了对某些代码的需要,但它会作为示例。
在您的 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
};
我会尝试将您正在调用的 getScope 和 getFonts 主体包装在
$scope.$apply(function(){ ...body here... });
确保在 self = this
之外声明任何函数。
将调用分配给要存储数据的值,然后return它。
var self = this;
self.data = [];
this.updateFonts = function() {
self.fonts = $http.get(BACKEND_URL+'/fonts').success(function(data) {
return data.data
});
return self.fonts
};
由于您使用的是 ui-router(我看到了 $state)。您可以在您所在的州使用决议和 return 承诺。
文档: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 变成承诺
this.getFonts = function(){
return $http.get(BACKEND_URL+'/fonts').success(function(data) {
self.fonts = data;
console.log('Library:: fonts updated', self.fonts);
});
}
有很多方法可以做到这一点,但我认为解决方法是最好的。
这确实是 promises 的目的。您的服务应该 return 一个承诺即被解决。您还可以简化您的服务:
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 的演示:
谢谢大家的回答!我最终混合使用了回调和承诺,如下所示:
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 });
});
我尝试了你的所有建议,但这是与我的其余代码一起使用的最佳建议。
我有以下服务:
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
,但我确信有一种更简洁的方法...
考虑传递回调函数。
服务:
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; });
这可以稍微整理一下,因为回调消除了对某些代码的需要,但它会作为示例。
在您的 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
};
我会尝试将您正在调用的 getScope 和 getFonts 主体包装在
$scope.$apply(function(){ ...body here... });
确保在 self = this
之外声明任何函数。
将调用分配给要存储数据的值,然后return它。
var self = this;
self.data = [];
this.updateFonts = function() {
self.fonts = $http.get(BACKEND_URL+'/fonts').success(function(data) {
return data.data
});
return self.fonts
};
由于您使用的是 ui-router(我看到了 $state)。您可以在您所在的州使用决议和 return 承诺。
文档: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 变成承诺
this.getFonts = function(){
return $http.get(BACKEND_URL+'/fonts').success(function(data) {
self.fonts = data;
console.log('Library:: fonts updated', self.fonts);
});
}
有很多方法可以做到这一点,但我认为解决方法是最好的。
这确实是 promises 的目的。您的服务应该 return 一个承诺即被解决。您还可以简化您的服务:
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 的演示:
谢谢大家的回答!我最终混合使用了回调和承诺,如下所示:
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 });
});
我尝试了你的所有建议,但这是与我的其余代码一起使用的最佳建议。