AngularJS:使用异步数据初始化服务
我有一个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()
,我将得到一个空指针异常。 据我所知,在这里和这里提出的一些其他问题可以告诉我,我有几个选择,但没有一个看起来很干净(也许我错过了一些东西):
安装服务与“运行”
当设置我的应用程序时请这样做:
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()返回一个承诺,所有代码都将有给我们then
与承诺互动。 我宁愿等到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.when('/path',{ resolve:{...}
?它可以使承诺方法更清洁一点:
在您的服务中披露承诺:
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的解决方案,以下是一个完整,简洁的纯角度解决方案:
(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的解决方案相比,这是一个轻微的增强,延迟取得配置直到文档准备就绪。 据我所知,没有理由推迟$ http呼叫。
单元测试
注意:当代码包含在app.js
文件中时,我发现这个解决方案在单元测试时不能很好地工作。 原因是上面的代码在加载JS文件时立即运行。 这意味着测试框架(在我的情况下是Jasmine)没有机会提供$http
的模拟实现。
我并不完全满意的解决方案是将此代码移至index.html
文件,因此Grunt / Karma / Jasmine单元测试基础结构不会看到它。
我使用了类似于@XMLilley描述的方法,但希望能够像使用$http
一样使用AngularJS服务来加载配置,并在不使用低级API或jQuery的情况下进行进一步初始化。
在路由上使用resolve
也不是一种选择,因为我需要这些值在我的应用程序启动时作为常量可用,即使在module.config()
块中。
我创建了一个加载配置的小型AngularJS应用程序,将它们设置为实际应用程序中的常量并引导它。
// define the module of your app
angular.module('MyApp', []);
// define the module of the bootstrap app
var bootstrapModule = angular.module('bootstrapModule', []);
// the bootstrapper service loads the config and bootstraps the specified app
bootstrapModule.factory('bootstrapper', function ($http, $log, $q) {
return {
bootstrap: function (appName) {
var deferred = $q.defer();
$http.get('/some/url')
.success(function (config) {
// set all returned values as constants on the app...
var myApp = angular.module(appName);
angular.forEach(config, function(value, key){
myApp.constant(key, value);
});
// ...and bootstrap the actual app.
angular.bootstrap(document, [appName]);
deferred.resolve();
})
.error(function () {
$log.warn('Could not initialize application, configuration could not be loaded.');
deferred.reject();
});
return deferred.promise;
}
};
});
// create a div which is used as the root of the bootstrap app
var appContainer = document.createElement('div');
// in run() function you can now use the bootstrapper service and shutdown the bootstrapping app after initialization of your actual app
bootstrapModule.run(function (bootstrapper) {
bootstrapper.bootstrap('MyApp').then(function () {
// removing the container will destroy the bootstrap app
appContainer.remove();
});
});
// make sure the DOM is fully loaded before bootstrapping.
angular.element(document).ready(function() {
angular.bootstrap(appContainer, ['bootstrapModule']);
});
请参阅以下操作(使用$timeout
而不是$http
): $http
://plnkr.co/edit/FYznxP3xe8dxzwxs37hi?p=preview
UPDATE
我会建议使用Martin Atkins和JBCP在下面描述的方法。
更新2
因为我在多个项目中需要它,所以我刚刚发布了一个处理此问题的bower模块:https://github.com/philippd/angular-deferred-bootstrap
从后端加载数据并在AngularJS模块上设置名为APP_CONFIG的常量的示例:
deferredBootstrapper.bootstrap({
element: document.body,
module: 'MyApp',
resolve: {
APP_CONFIG: function ($http) {
return $http.get('/api/demo-config');
}
}
});
链接地址: http://www.djcxy.com/p/77559.html
上一篇: AngularJS : Initialize service with asynchronous data
下一篇: AngularJS : The correct way of binding to a service properties