Angular factory возвращает обещание
Когда мое приложение запускается, я загружаю некоторые настройки с сервера. Большинство моих контроллеров нуждаются в этом, прежде чем что-нибудь полезное может быть сделано. Я хочу как можно больше упростить код контроллера. Моя попытка, которая не работает, выглядит примерно так:
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
}]);
Я бы хотел, чтобы у меня не было много настроек. Затем (function()...) везде.
Любые идеи о том, как решить эту проблему красивым способом?
Ответы
Ответ 1
$http self return обещание, что вам не нужно связывать его внутри $q, это не очень хорошая практика и рассматривается как Anti Pattern.
Использование: -
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 (не рекомендуется).
Ответ 2
Где-то вам нужно будет "подождать".
Единственный встроенный в Angular способ полностью освободить контроллер от необходимости самостоятельно ждать загрузки асинхронных данных - это создать экземпляр контроллера со $routeProvider
resolve
маршрута $routeProvider
(или альтернативным $stateProvider
для ui.router
). Это будет запускать контроллер только тогда, когда все обещания будут разрешены, и разрешенные данные будут введены.
Итак, альтернатива 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
должен кешировать обещание, чтобы не нужно было повторять HTTP-запрос. Обратите внимание, что, как упоминалось в другом ответе, нет необходимости в $q.defer
когда используемый вами API (например, $http
) уже возвращает обещание:
.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>
Ответ 3
Fo ui-router это легко сделать с наличием корневого состояния приложения, по крайней мере, с этим минимальным определением
$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});
}
}
})
После этого вы можете наследовать все состояния приложения из этого корневого состояния и
- Все контроллеры будут выполняться только после загрузки настроек.
- Все контроллеры получат доступ к настройкам разрешенному значению как возможному впрыску.
Как упоминалось выше, решение также работает для исходного ng-route, но поскольку оно не поддерживает вложенный подход, это не так полезно, как для ui-router.
Ответ 4
Вы можете вручную загрузить приложение после загрузки параметров.
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 документацию по загрузке.