我有一个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变量闻起来很糟糕。
这是我唯一的选择吗?这些选项中是否有一个比其他选项更好?我知道这是一个相当长的问题,但我想表明我已经尝试了所有的选择。任何指导都将不胜感激。
你可以在应用的。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的数据。
我也遇到了同样的问题:我喜欢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文件加载配置为例。