我有一个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变量闻起来很糟糕。

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


当前回答

所以我找到了一个解决方案。我创建了一个angularJS服务,我们称之为MyDataRepository,并为它创建了一个模块。然后,我从服务器端控制器提供这个javascript文件:

HTML:

<script src="path/myData.js"></script>

服务器端:

@RequestMapping(value="path/myData.js", method=RequestMethod.GET)
public ResponseEntity<String> getMyDataRepositoryJS()
{
    // Populate data that I need into a Map
    Map<String, String> myData = new HashMap<String,String>();
    ...
    // Use Jackson to convert it to JSON
    ObjectMapper mapper = new ObjectMapper();
    String myDataStr = mapper.writeValueAsString(myData);

    // Then create a String that is my javascript file
    String myJS = "'use strict';" +
    "(function() {" +
    "var myDataModule = angular.module('myApp.myData', []);" +
    "myDataModule.service('MyDataRepository', function() {" +
        "var myData = "+myDataStr+";" +
        "return {" +
            "getData: function () {" +
                "return myData;" +
            "}" +
        "}" +
    "});" +
    "})();"

    // Now send it to the client:
    HttpHeaders responseHeaders = new HttpHeaders();
    responseHeaders.add("Content-Type", "text/javascript");
    return new ResponseEntity<String>(myJS , responseHeaders, HttpStatus.OK);
}

然后我可以在任何需要的地方注入MyDataRepository:

someOtherModule.service('MyOtherService', function(MyDataRepository) {
    var myData = MyDataRepository.getData();
    // Do what you have to do...
}

这对我来说很有效,但如果有人有任何反馈,我愿意接受。 }

其他回答

你可以在应用的。config中为路由创建resolve对象,在函数中传入$q (promise对象)和你所依赖的服务的名称,并在服务的$http回调函数中解析promise,如下所示:

路由配置

app.config(function($routeProvider){
    $routeProvider
     .when('/',{
          templateUrl: 'home.html',
          controller: 'homeCtrl',
          resolve:function($q,MyService) {
                //create the defer variable and pass it to our service
                var defer = $q.defer();
                MyService.fetchData(defer);
                //this will only return when the promise
                //has been resolved. MyService is going to
                //do that for us
                return defer.promise;
          }
      })
}

在调用deferred .resolve()之前,Angular不会渲染模板或使控制器可用。我们可以在我们的服务中做到:

服务

app.service('MyService',function($http){
       var MyService = {};
       //our service accepts a promise object which 
       //it will resolve on behalf of the calling function
       MyService.fetchData = function(q) {
             $http({method:'GET',url:'data.php'}).success(function(data){
                 MyService.data = data;
                 //when the following is called it will
                 //release the calling function. in this
                 //case it's the resolve function in our
                 //route config
                 q.resolve();
             }
       }

       return MyService;
});

现在MyService已经将数据分配给了它的data属性,路由解析对象中的承诺也已经解析,我们的路由控制器开始工作,我们可以将服务中的数据分配给我们的控制器对象。

控制器

  app.controller('homeCtrl',function($scope,MyService){
       $scope.servicedata = MyService.data;
  });

现在控制器范围内的所有绑定都将能够使用来自MyService的数据。

所以我找到了一个解决方案。我创建了一个angularJS服务,我们称之为MyDataRepository,并为它创建了一个模块。然后,我从服务器端控制器提供这个javascript文件:

HTML:

<script src="path/myData.js"></script>

服务器端:

@RequestMapping(value="path/myData.js", method=RequestMethod.GET)
public ResponseEntity<String> getMyDataRepositoryJS()
{
    // Populate data that I need into a Map
    Map<String, String> myData = new HashMap<String,String>();
    ...
    // Use Jackson to convert it to JSON
    ObjectMapper mapper = new ObjectMapper();
    String myDataStr = mapper.writeValueAsString(myData);

    // Then create a String that is my javascript file
    String myJS = "'use strict';" +
    "(function() {" +
    "var myDataModule = angular.module('myApp.myData', []);" +
    "myDataModule.service('MyDataRepository', function() {" +
        "var myData = "+myDataStr+";" +
        "return {" +
            "getData: function () {" +
                "return myData;" +
            "}" +
        "}" +
    "});" +
    "})();"

    // Now send it to the client:
    HttpHeaders responseHeaders = new HttpHeaders();
    responseHeaders.add("Content-Type", "text/javascript");
    return new ResponseEntity<String>(myJS , responseHeaders, HttpStatus.OK);
}

然后我可以在任何需要的地方注入MyDataRepository:

someOtherModule.service('MyOtherService', function(MyDataRepository) {
    var myData = MyDataRepository.getData();
    // Do what you have to do...
}

这对我来说很有效,但如果有人有任何反馈,我愿意接受。 }

基于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单元测试基础结构就看不到它了,我对此并不完全满意。

获取任何初始化最简单的方法使用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;
    });
  };

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

你看过$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