我有一个AngularJS服务,我想用一些异步数据初始化它。就像这样:

myModule.service('MyService', function($http) {
    var myData = null;

    $http.get('data.json').success(function (data) {
        myData = data;
    });

    return {
        setData: function (data) {
            myData = data;
        },
        doStuff: function () {
            return myData.getSomeData();
        }
    };
});

显然,这将不起作用,因为如果有人试图在myData返回之前调用doStuff(),我将得到一个空指针异常。就我所知,从阅读这里提出的其他一些问题中,我有一些选择,但没有一个看起来很干净(也许我遗漏了一些东西):

带有“run”的安装服务

当设置我的应用程序这样做:

myApp.run(function ($http, MyService) {
    $http.get('data.json').success(function (data) {
        MyService.setData(data);
    });
});

然后我的服务看起来像这样:

myModule.service('MyService', function() {
    var myData = null;
    return {
        setData: function (data) {
            myData = data;
        },
        doStuff: function () {
            return myData.getSomeData();
        }
    };
});

这在某些时候是有效的,但如果异步数据花费的时间恰好比所有东西初始化所需的时间长,那么当我调用doStuff()时,我会得到一个空指针异常。

使用承诺对象

这可能行得通。唯一的缺点它到处我调用MyService,我必须知道doStuff()返回一个承诺和所有的代码将不得不我们然后与承诺交互。我宁愿只是等待,直到myData返回之前加载我的应用程序。

手动启动

angular.element(document).ready(function() {
    $.getJSON("data.json", function (data) {
       // can't initialize the data here because the service doesn't exist yet
       angular.bootstrap(document);
       // too late to initialize here because something may have already
       // tried to call doStuff() and would have got a null pointer exception
    });
});

全局Javascript Var 我可以将我的JSON直接发送到一个全局Javascript变量:

HTML:

<script type="text/javascript" src="data.js"></script>

data.js:

var dataForMyService = { 
// myData here
};

然后在初始化MyService时可用:

myModule.service('MyService', function() {
    var myData = dataForMyService;
    return {
        doStuff: function () {
            return myData.getSomeData();
        }
    };
});

这也可以工作,但我有一个全局javascript变量闻起来很糟糕。

这是我唯一的选择吗?这些选项中是否有一个比其他选项更好?我知道这是一个相当长的问题,但我想表明我已经尝试了所有的选择。任何指导都将不胜感激。


当前回答

你看过$routeProvider吗?当(/路径,{解决:{…}?它可以让承诺的方式更简洁:

在你的服务中暴露一个承诺:

app.service('MyService', function($http) {
    var myData = null;

    var promise = $http.get('data.json').success(function (data) {
      myData = data;
    });

    return {
      promise:promise,
      setData: function (data) {
          myData = data;
      },
      doStuff: function () {
          return myData;//.getSomeData();
      }
    };
});

在路由配置中添加resolve:

app.config(function($routeProvider){
  $routeProvider
    .when('/',{controller:'MainCtrl',
    template:'<div>From MyService:<pre>{{data | json}}</pre></div>',
    resolve:{
      'MyServiceData':function(MyService){
        // MyServiceData will also be injectable in your controller, if you don't want this you could create a new promise with the $q service
        return MyService.promise;
      }
    }})
  }):

在所有依赖项解析之前,你的控制器不会被实例化:

app.controller('MainCtrl', function($scope,MyService) {
  console.log('Promise is now resolved: '+MyService.doStuff().data)
  $scope.data = MyService.doStuff();
});

我在plnkr上举了一个例子:http://plnkr.co/edit/GKg21XH0RwCMEQGUdZKH?p=preview

其他回答

你看过$routeProvider吗?当(/路径,{解决:{…}?它可以让承诺的方式更简洁:

在你的服务中暴露一个承诺:

app.service('MyService', function($http) {
    var myData = null;

    var promise = $http.get('data.json').success(function (data) {
      myData = data;
    });

    return {
      promise:promise,
      setData: function (data) {
          myData = data;
      },
      doStuff: function () {
          return myData;//.getSomeData();
      }
    };
});

在路由配置中添加resolve:

app.config(function($routeProvider){
  $routeProvider
    .when('/',{controller:'MainCtrl',
    template:'<div>From MyService:<pre>{{data | json}}</pre></div>',
    resolve:{
      'MyServiceData':function(MyService){
        // MyServiceData will also be injectable in your controller, if you don't want this you could create a new promise with the $q service
        return MyService.promise;
      }
    }})
  }):

在所有依赖项解析之前,你的控制器不会被实例化:

app.controller('MainCtrl', function($scope,MyService) {
  console.log('Promise is now resolved: '+MyService.doStuff().data)
  $scope.data = MyService.doStuff();
});

我在plnkr上举了一个例子:http://plnkr.co/edit/GKg21XH0RwCMEQGUdZKH?p=preview

基于Martin Atkins的解决方案,这里有一个完整、简洁的纯angular解决方案:

(function() {
  var initInjector = angular.injector(['ng']);
  var $http = initInjector.get('$http');
  $http.get('/config.json').then(
    function (response) {
      angular.module('config', []).constant('CONFIG', response.data);

      angular.element(document).ready(function() {
          angular.bootstrap(document, ['myApp']);
        });
    }
  );
})();

这个解决方案使用一个自动执行的匿名函数来获取$http服务,请求配置,并在配置可用时将其注入到名为config的常量中。

完成之后,我们等待文档准备好,然后引导Angular应用。

这是对Martin的解决方案的轻微改进,Martin的解决方案将获取配置延迟到文档准备好之后。据我所知,没有理由为此延迟$http调用。

单元测试

注意:我发现当代码包含在app.js文件中时,这种解决方案在进行单元测试时效果不佳。这样做的原因是上述代码在加载JS文件时立即运行。这意味着测试框架(在我的例子中是Jasmine)没有机会提供$http的模拟实现。

我的解决方案是将这段代码移到index.html文件中,这样Grunt/Karma/Jasmine单元测试基础结构就看不到它了,我对此并不完全满意。

我也遇到了同样的问题:我喜欢resolve对象,但它只适用于ng-view的内容。如果你有一个存在于ng-view之外的控制器(比如说顶层导航),并且需要在路由开始发生之前用数据进行初始化,那该怎么办?我们如何避免在服务器端浪费时间呢?

Use manual bootstrap and an angular constant. A naiive XHR gets you your data, and you bootstrap angular in its callback, which deals with your async issues. In the example below, you don't even need to create a global variable. The returned data exists only in angular scope as an injectable, and isn't even present inside of controllers, services, etc. unless you inject it. (Much as you would inject the output of your resolve object into the controller for a routed view.) If you prefer to thereafter interact with that data as a service, you can create a service, inject the data, and nobody will ever be the wiser.

例子:

//First, we have to create the angular module, because all the other JS files are going to load while we're getting data and bootstrapping, and they need to be able to attach to it.
var MyApp = angular.module('MyApp', ['dependency1', 'dependency2']);

// Use angular's version of document.ready() just to make extra-sure DOM is fully 
// loaded before you bootstrap. This is probably optional, given that the async 
// data call will probably take significantly longer than DOM load. YMMV.
// Has the added virtue of keeping your XHR junk out of global scope. 
angular.element(document).ready(function() {

    //first, we create the callback that will fire after the data is down
    function xhrCallback() {
        var myData = this.responseText; // the XHR output

        // here's where we attach a constant containing the API data to our app 
        // module. Don't forget to parse JSON, which `$http` normally does for you.
        MyApp.constant('NavData', JSON.parse(myData));

        // now, perform any other final configuration of your angular module.
        MyApp.config(['$routeProvider', function ($routeProvider) {
            $routeProvider
              .when('/someroute', {configs})
              .otherwise({redirectTo: '/someroute'});
          }]);

        // And last, bootstrap the app. Be sure to remove `ng-app` from your index.html.
        angular.bootstrap(document, ['NYSP']);
    };

    //here, the basic mechanics of the XHR, which you can customize.
    var oReq = new XMLHttpRequest();
    oReq.onload = xhrCallback;
    oReq.open("get", "/api/overview", true); // your specific API URL
    oReq.send();
})

现在,你的NavData常量存在了。继续并将其注入到控制器或服务中:

angular.module('MyApp')
    .controller('NavCtrl', ['NavData', function (NavData) {
        $scope.localObject = NavData; //now it's addressable in your templates 
}]);

当然,使用纯XHR对象会减少$http或JQuery为您提供的一些细节,但是这个示例没有特殊的依赖关系,至少对于简单的get来说是这样。如果您希望为您的请求提供更强大的功能,可以加载一个外部库来帮助您。但我认为在这种情况下不可能访问angular的$http或其他工具。

(SO相关职位)

“手动引导”的情况可以通过在引导之前手动创建注入器来访问Angular服务。这个初始注入器是独立的(不附加到任何元素),只包含被加载模块的一个子集。如果你只需要核心的Angular服务,只加载ng就足够了,像这样:

angular.element(document).ready(
    function() {
        var initInjector = angular.injector(['ng']);
        var $http = initInjector.get('$http');
        $http.get('/config.json').then(
            function (response) {
               var config = response.data;
               // Add additional services/constants/variables to your app,
               // and then finally bootstrap it:
               angular.bootstrap(document, ['myApp']);
            }
        );
    }
);

例如,您可以使用模块。常量机制,使数据可用于你的应用程序:

myApp.constant('myAppConfig', data);

这个myAppConfig现在可以像任何其他服务一样被注入,特别是在配置阶段可用:

myApp.config(
    function (myAppConfig, someService) {
        someService.config(myAppConfig.someServiceConfig);
    }
);

或者,对于一个较小的应用程序,您可以直接将全局配置注入到您的服务中,代价是在整个应用程序中传播有关配置格式的知识。

当然,由于这里的异步操作将阻塞应用程序的引导,从而阻塞模板的编译/链接,因此使用ng-cloak指令来防止在工作期间显示未解析的模板是明智的。你也可以在DOM中提供一些加载指示,通过提供一些HTML,这些HTML只在AngularJS初始化之前显示:

<div ng-if="initialLoad">
    <!-- initialLoad never gets set, so this div vanishes as soon as Angular is done compiling -->
    <p>Loading the app.....</p>
</div>
<div ng-cloak>
    <!-- ng-cloak attribute is removed once the app is done bootstrapping -->
    <p>Done loading the app!</p>
</div>

我在Plunker上创建了这个方法的完整的工作示例,以从静态JSON文件加载配置为例。

获取任何初始化最简单的方法使用ng-init目录。

只要把ng-init div scope放在你想获取init数据的地方

index . html

<div class="frame" ng-init="init()">
    <div class="bit-1">
      <div class="field p-r">
        <label ng-show="regi_step2.address" class="show-hide c-t-1 ng-hide" style="">Country</label>
        <select class="form-control w-100" ng-model="country" name="country" id="country" ng-options="item.name for item in countries" ng-change="stateChanged()" >
        </select>
        <textarea class="form-control w-100" ng-model="regi_step2.address" placeholder="Address" name="address" id="address" ng-required="true" style=""></textarea>
      </div>
    </div>
  </div>

index.js

$scope.init=function(){
    $http({method:'GET',url:'/countries/countries.json'}).success(function(data){
      alert();
           $scope.countries = data;
    });
  };

注意:如果您在不同的地方没有相同的代码,您可以使用这种方法。