我正在开发一个像这样设置的视频系列应用:
angular.module('videoSeries', ['ngAnimate', 'ui.router'])
.config(config)
.factory('episodes', episodesFactory)
.controller('MainCtrl', MainCtrl)
.controller('EpisodeCtrl', EpisodeCtrl);
工厂'剧集'加载两个控制器都需要读取的JSONP文件。为了确保控制器等到JSONP成功返回,我在工厂设置了一个承诺(as suggested here):
episodesFactory.$inject = ['$http', '$q'];
function episodesFactory($http, $q) {
var pub = {},
jsonUrl = 'http://i.cdn.turner.com/nba/nba/.element/media/2.0/teamsites/warriors/json/json-wgtv.js?callback=JSON_CALLBACK' + (new Date().getTime()),
cachedResponse;
pub.getData = function() {
var deferred = $q.defer();
if (cachedResponse) {
deferred.resolve(cachedResponse);
}
else {
$http.jsonp(jsonUrl);
window.jsonCallbackWGTV = function(data) {
cachedResponse = data;
deferred.resolve(data);
}
}
return deferred.promise;
}
return pub;
}
然后两个控制器在.then之后执行,如下所示:
MainCtrl.$inject = ['$scope', 'episodes'];
function MainCtrl($scope, episodes) {
episodes.getData().then(function(data) {
$scope.episodes = data.episodes;
});
}
EpisodeCtrl.$inject = ['$scope', '$stateParams', 'episodes'];
function EpisodeCtrl($scope, $stateParams, episodes) {
episodes.getData().then(function(data) {
$scope.episode = data.episodes[$stateParams.episodeIndex];
});
}
如果只有一个控制器一次调用.getData(),但是如果两个控制器同时调用它(如果直接加载到剧集状态会发生这种情况),这样就可以正常工作了,看起来就像忘记了一个承诺。
Here is a plunker。请注意,为了查看问题,请单击一个剧集,然后直接重新加载到该路径,但我不确定如何在浏览器中执行此操作,因为URL未更新。
答案 0 :(得分:1)
您的问题基本上是由远程资源始终返回其jsonCallbackWGTV() {...}
中包含的数据引起的,导致您的两个同时呼叫争夺window.jsonCallbackWGTV()
。 最后笑的人,笑得最响亮。
JSONP资源应该真正允许客户端指定回调的名称,但这可能是你无法控制的。
应该通过缓存延迟而不是传递给它的数据来克服问题。因此,原始的.getData()
调用和所有后续调用都可以返回从相同的延迟派生的承诺。
episodesFactory.$inject = ['$http', '$q'];
function episodesFactory($http, $q) {
var jsonUrl = 'http://i.cdn.turner.com/nba/nba/.element/media/2.0/teamsites/warriors/json/json-wgtv.js',
deferred;
return {
getData: function() {
if (!deferred) {
deferred = $q.defer();
$http.jsonp(jsonUrl);
window.jsonCallbackWGTV = function(data) {
deferred.resolve(data);
delete window.jsonCallbackWGTV;
}
}
return deferred.promise;
}
};
}
更典型的是,您将缓存延迟的承诺而不是延迟本身,但jsonCallbackWGTV
问题使得必须保留对延迟的引用,并且缓存的延迟也可以用于这两个目的。
答案 1 :(得分:1)
您可以通过在根状态下使用解决方案来回避问题。所有子州都将继承已解决的数据。
.state('root', {
url: '',
abstract: true,
views: {
'episodesList': {
templateUrl: '/episodelist.html',
controller: 'MainCtrl'
}
},
resolve: {
'cachedEpisodes': ['episodes', function(episodes){
return episodes.getData();
}]
}
}
在这种情况下,解析只应在任何子状态激活此根状态时发生一次。
编辑: 您不需要在配置中包含剧集工厂。状态解析是对象映射。每个密钥都是一个名称,可在该状态下的任何控制器或子控制器的注入中使用。
在状态变为活动状态之前,解析函数不会执行,此时它将可以访问所有运行时(非配置)注入。
我将函数包装在数组中可能看起来有点奇怪。数组的每个值都是注入的名称,数组的最终值必须是注入它们的函数。这是以角度启用的,以帮助防止优化器的问题,这些优化器会破坏参数名称(否则将用于确定注入名称)。
哦,在路由更改完成之前,任何返回promises的解析函数都将通过angular完全解析,并且它们的解析值是注入控制器的值。
MainCtrl.$inject = ['$scope', 'cachedEpisodes'];
function MainCtrl($scope, data) {
$scope.episodes = data.episodes;
}
EpisodeCtrl.$inject = ['$scope', '$stateParams', 'cachedEpisodes'];
function EpisodeCtrl($scope, $stateParams, data) {
var i = 0, len = data.episodes.length, episode;
for (i = 0; i < len; i++) {
if (data.episodes[i].season === $stateParams.season && data.episodes[i].episode === $stateParams.episode) {
episode = data.episodes[i];
break;
}
}
$scope.episode = episode;
}