当我的应用程序启动时,我从服务器加载一些设置。在做任何有用的事情之前,我的大多数控制器都需要这个。我想尽可能地简化控制器的代码。我的尝试无效,是这样的:
app.factory('settings', ['$rootScope', '$http', '$q', function($rootScope, $http, $q) {
var deferred = $q.defer();
$http.get('/api/public/settings/get').success(function(data) {
$rootScope.settings = data;
deferred.resolve();
});
return deferred.promise;
}]);
app.controller('SomeCtrl', ['$rootScope', 'settings', function($rootScope, settings) {
// Here I want settings to be available
}]);
我想避免在任何地方设置很多settings.then(function()...)。
关于如何以一种好的方式解决这个问题的任何想法?
答案 0 :(得分:19)
$ http本身返回承诺你不需要在$ q中绑定它这不是一个好习惯,并被视为反模式。
使用: -
app.factory('settings', ['$rootScope', '$http', '$q', function($rootScope, $http) {
return $http.get('/api/public/settings/get')
}]);
app.controller('SomeCtrl', ['settings',$scope, function(settings,$scope) {
settings.then(function(result){
$scope.settings=result.data;
});
}]);
你可以这样做: -
app.factory('settings', ['$rootScope', '$http', '$q', function($rootScope, $http, $q) {
var deferred = $q.defer();
$http.get('/api/public/settings/get').success(function(data) {
deferred.resolve(data);
});
return deferred.promise;
}]);
app.controller('SomeCtrl', ['$scope', 'settings', function($scope, settings) {
settings.then(function(data){
$scope.settings=data;
})
}]);
如果你想要它,请不要超载$ rootScope,你需要使用$ watch来查看$ rootScope中的更改(不推荐)。
答案 1 :(得分:14)
某处你需要“等待”。
Angular中唯一能够完全免除控制器自身等待加载异步数据的内置方法是使用$routeProvider
的路由resolve
属性实例化控制器(或$stateProvider
的替代ui.router
。只有在解决了所有的promises后才会运行控制器,并且会注入已解析的数据。
所以, ng-route 替代方案 - plunker:
$routeProvider.when("/", {
controller: "SomeCtrl",
templateUrl: "someTemplate.html",
resolve: {
settings: function(settingsSvc){
return settingsSvc.load(); // I renamed the loading function for clarity
}
});
然后,在SomeCtrl
中,您可以添加settings
作为可注射依赖项:
.controller("SomeCtrl", function($scope, settings){
if (settings.foo) $scope.bar = "foo is on";
})
这将“等待”在someTemplate
中加载<div ng-view></div>
,直到设置得到解决。
settingsSvc
应该缓存promise,这样就不需要重做HTTP请求了。请注意,正如另一个答案中所提到的,当您使用的API(如$q.defer
)已经返回承诺时,$http
不需要{<1}}:
.factory("settingsSvc", function($http){
var svc = {settings: {}};
var promise = $http.get('/api/public/settings/get').success(function(data){
svc.settings = data; // optionally set the settings data here
});
svc.load = function(){
return promise;
}
return svc;
});
另一种方法,如果您不喜欢ngRoute
方式,可能是settings
服务在$rootScope
广播时设置为加载,控制器可以对它做出反应并做任何事情。但这似乎比.then
“更重”。
我猜第三种方式 - plunker - 只有当所有依赖项都已预加载时,才会让应用级控制器“启用”应用的其余部分:
.controller("AppCtrl", function($q, settingsSvc, someOtherService){
$scope.loaded = false;
$q.all([settingsSvc.load, someOtherService.prefetch]).then(function(){
$scope.loaded = true;
});
});
在视图中,使用ng-if
切换loaded
:
<body ng-controller="AppCtrl">
<div ng-if="!loaded">loading app...</div>
<div ng-if="loaded">
<div ng-controller="MainCtrl"></div>
<div ng-controller="MenuCtrl"></div>
</div>
</body>
答案 2 :(得分:1)
使用至少具有此最小定义的应用程序根状态
,可以轻松完成此操作$stateProvider
.state('app', {
abstract: true,
template: '<div ui-view></div>'
resolve: {
settings: function($http){
return $http.get('/api/public/settings/get')
.then(function(response) {return response.data});
}
}
})
在此之后,您可以使所有应用程序状态继承自此根状态和
如上所述 resolve 也适用于原始的 ng-route ,但由于它不支持嵌套,因此该方法不如ui-router有用。
答案 3 :(得分:1)
您可以在加载设置后手动引导应用程序。
var initInjector = angular.injector(["ng"]);
var $http = initInjector.get("$http");
var $rootScope = initInjector.get("$rootScope");
$http.get('/api/public/settings/get').success(function(data) {
$rootScope.settings = data;
angular.element(document).ready(function () {
angular.bootstrap(document, ["app"]);
});
});
在这种情况下,只有在加载设置后才会运行整个应用程序。
有关详细信息,请参阅Angular bootstrap documentation