Angular工厂返回承诺

Mar*_*son 31 angularjs

当我的应用程序启动时,我从服务器加载一些设置.在做任何有用的事情之前,我的大多数控制器都需要这个.我想尽可能地简化控制器的代码.我的尝试无效,是这样的:

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
}]);
Run Code Online (Sandbox Code Playgroud)

我想避免有很多settings.then(function()...)无处不在.

关于如何以一种很好的方式解决这个问题的任何想法?

squ*_*oid 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;
          });
    }]);
Run Code Online (Sandbox Code Playgroud)

你的方式可以做到: -

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;
       })
}]);
Run Code Online (Sandbox Code Playgroud)

如果你需要,请不要重载$ rootScope,你需要使用$ watch来查看$ rootScope中的更改(不推荐).

  • 正如我写的那样,我想避免使用"settings.then"-part.理想情况下,我希望控制器SomeCtrl等到设置加载完毕.那可能吗? (3认同)

New*_*Dev 14

在某个地方你需要"等待".

Angular中唯一能够完全免除控制器自身等待加载异步数据的内置方法是使用$routeProvider路由resolve属性(或替代$stateProvider方法ui.router)实例化控制器.这将仅在解析所有promise时运行控制器,并且将注入已解析的数据.

所以,ng-route alternative - plunker:

$routeProvider.when("/", {
   controller: "SomeCtrl",
   templateUrl: "someTemplate.html",
   resolve: {
     settings: function(settingsSvc){
       return settingsSvc.load(); // I renamed the loading function for clarity
     }
   });
Run Code Online (Sandbox Code Playgroud)

然后,SomeCtrl您可以添加settings为可注入依赖项:

.controller("SomeCtrl", function($scope, settings){
   if (settings.foo) $scope.bar = "foo is on";
})
Run Code Online (Sandbox Code Playgroud)

这将"等待"加载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;
});
Run Code Online (Sandbox Code Playgroud)

如果您不喜欢这种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;
   });
});
Run Code Online (Sandbox Code Playgroud)

在视图中,切换ng-ifloaded:

<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>
Run Code Online (Sandbox Code Playgroud)