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

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


当前回答

此外,在执行实际控制节点之前,还可以使用以下技术实现全局发放业务:https://stackoverflow.com/a/27050497/1056679。只是全局解析你的数据,然后在运行块中传递给你的服务。

其他回答

你可以在应用的。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...
}

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

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

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

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

我也遇到了同样的问题:我喜欢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相关职位)