Подтвердить что ты не робот

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()...) везде.

Любые идеи о том, как решить эту проблему красивым способом?

4b9b3361

Ответ 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 документацию по загрузке.