我有一个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()
,我将得到一个空指针异常 . 据我所知,通过阅读其他一些问题here和here我有几个选择,但它们都没有看起来很干净(也许我错过了一些东西):
Setup Service with "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()
时,我会得到一个空指针异常
Use promise objects
这可能会奏效 . 我称之为MyService的唯一缺点是我必须知道doStuff()返回一个promise,所有代码都必须让我们 then
与promise进行交互 . 在加载我的应用程序之前,我宁愿等到myData返回 .
Manual Bootstrap
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
});
});
Global Javascript Var 我可以将我的JSON直接发送到全局Javascript变量:
HTML:
<script type="text/javascript" src="data.js"></script>
data.js:
var dataForMyService = {
// myData here
};
然后在初始化_86141时可用:
myModule.service('MyService', function() {
var myData = dataForMyService;
return {
doStuff: function () {
return myData.getSomeData();
}
};
});
这也可以,但是我有一个全局的javascript变量,闻起来很糟糕 .
这些是我唯一的选择吗?这些选项中的一个比其他选项更好吗?我知道这是一个很长的问题,但我想表明我试图探索我的所有选择 . 非常感谢任何指导 .
10 回答
你看过$routeProvider.when('/path',{ resolve:了吗?它可以使承诺更清洁:
在您的服务中公开承诺:
将
resolve
添加到您的路由配置:在解决所有依赖项之前,您的控制器不会被实例化:
我在plnkr做了一个例子:http://plnkr.co/edit/GKg21XH0RwCMEQGUdZKH?p=preview
基于Martin Atkins的解决方案,这是一个完整,简洁的纯Angular解决方案:
此解决方案使用自执行匿名函数来获取$ http服务,请求配置,并在可用时将其注入名为CONFIG的常量中 .
完成后,我们等到文档准备就绪,然后引导Angular应用程序 .
这是对Martin解决方案的略微改进,该解决方案推迟获取配置,直到文档准备就绪 . 据我所知,没有理由推迟$ http调用 .
Unit Testing
注意:我发现当代码包含在
app.js
文件中时,单元测试时此解决方案不能正常工作 . 原因是上面的代码在加载JS文件时立即运行 . 这意味着测试框架(在我的情况下是Jasmine)没有机会提供$http
的模拟实现 .我的解决方案,我并不完全满意,是将此代码移动到我们的
index.html
文件,因此Grunt / Karma / Jasmine单元测试基础架构看不到它 .此外,在执行实际控制器之前,您可以使用以下技术全局配置服务:https://stackoverflow.com/a/27050497/1056679 . 只需全局解析数据,然后将其传递给
run
块中的服务 ."manual bootstrap"案例可以通过在引导之前手动创建注入器来获取对Angular服务的访问权限 . 此初始注入器将独立存在(不附加到任何元素),并且仅包括已加载的模块的子集 . 如果您只需要核心Angular服务,只需加载
ng
即可,如下所示:例如,您可以使用
module.constant
机制为您的应用提供数据:现在可以像任何其他服务一样注入
myAppConfig
,特别是在配置阶段它可用:或者,对于较小的应用程序,您可以直接将全局配置注入您的服务,但代价是在整个应用程序中传播有关配置格式的知识 .
当然,由于此处的异步操作将阻止应用程序的引导程序,从而阻止模板的编译/链接,因此使用
ng-cloak
指令防止未分析的模板在工作期间出现是明智的 . 您还可以在DOM中提供某种加载指示,方法是提供一些只在AngularJS初始化之前显示的HTML:我在Plunker上创建了这种方法的a complete, working example,从静态JSON文件加载配置作为示例 .
获取任何初始化的最简单方法是使用ng-init目录 .
只需将ng-init div范围放在要获取init数据的位置即可
index.html
index.js
所以我找到了解决方案 . 我创建了一个angularJS服务,我们称之为MyDataRepository,我为它创建了一个模块 . 然后我从服务器端控制器提供这个javascript文件:
HTML:
Server-side:
我可以然后在我需要的地方注入MyDataRepository:
这对我来说很有用,但如果有人有任何反馈,我愿意接受任何反馈 . }
我使用了与@XMLilley描述的方法类似的方法,但希望能够使用像
$http
这样的AngularJS服务来加载配置并在不使用低级API或jQuery的情况下进行进一步的初始化 .在路由上使用
resolve
也不是一个选项,因为我需要在启动应用程序时将值作为常量使用,即使在module.config()
块中也是如此 .我创建了一个小的AngularJS应用程序来加载配置,将它们设置为实际应用程序上的常量并引导它 .
在此处查看它(使用
$timeout
而不是$http
):http://plnkr.co/edit/FYznxP3xe8dxzwxs37hi?p=previewUPDATE
我建议使用Martin Atkins和JBCP下面介绍的方法 .
UPDATE 2
因为我需要在多个项目中使用它,所以我刚刚发布了一个用于处理此问题的bower模块:https://github.com/philippd/angular-deferred-bootstrap
从后端加载数据并在AngularJS模块上设置名为APP_CONFIG的常量的示例:
您可以做的是在你的.config中为app创建路由的解析对象,并在函数传递$ q(promise对象)和你所依赖的服务的名称,并解决在服务中$ http的回调函数如下:
ROUTE CONFIG
在调用defer.resolve()之前,Angular不会渲染模板或使控制器可用 . 我们可以在服务中做到这一点:
服务
既然MyService已经为其数据属性分配了数据,并且路径解析对象中的promise已经解析,我们的路由控制器就会生效,我们可以将服务中的数据分配给控制器对象 .
CONTROLLER
现在,我们在控制器范围内的所有绑定都将能够使用源自MyService的数据 .
我有同样的问题:我喜欢
resolve
对象,但这只适用于ng-view的内容 . 如果你有控制器(对于顶级导航,假设)存在于ng-view之外并且需要在路由开始发生之前用数据初始化怎么办?我们如何避免在服务器端乱搞只是为了让它工作?Use manual bootstrap and an angular constant . 一个简单的XHR可以获取您的数据,并在其回调中引导角度,这将处理您的异步问题 . 在下面的示例中,除非您注入,否则您甚至不会出现在控制器,服务等内部 . (就像你将
resolve
对象的输出注入控制器以获得路由视图一样 . )如果您希望此后作为服务与该数据进行交互,您可以创建服务,注入数据,并且没有人会成为聪明 .例:
现在,您的
NavData
常量存在 . 继续将其注入控制器或服务:当然,使用裸XHR对象会剥夺
$http
或JQuery为您提供的许多细节,但这个示例没有特殊的依赖关系,至少对于一个简单的get
. 如果您希望为请求提供更多功能,请加载外部库以帮助您 . 但是我不能在这种情况下访问angular的$http
或其他工具 .(SO related post)
您可以使用
JSONP
异步加载服务数据 . JSONP请求将在初始页面加载期间生成,结果将在应用程序启动之前可用 . 这样,您就不必使用冗余解析来扩展路由 .你的HTML看起来像这样: