我的Angular应用程序中发生了一个奇怪的问题,我认为Angular的异步性是原因,但我不确定如何解决它。
在.run
我有以下内容:
app.run(function ($user, $soundcloud) {
$user.set();
$soundcloud.set();
});
然后是两个服务......
app.service('$user', ['$http', function ($http) {
var currentUser;
this.set = function () {
$http.get('/users/active/profile')
.success(function (obj) {
currentUser = obj;
})
.error(function () {
console.log('ERR');
})
}
this.fetch = function () {
return currentUser;
}
}]);
app.service('$soundcloud', ['$http', function ($http) {
var sc;
this.set = function () {
$http.get('/users/active/services/soundcloud')
.success(function (obj) {
sc = obj;
})
.error(function () {
console.log('Error fetching soundcloud feed')
})
}
this.fetch = function () {
return sc;
}
}]);
我遇到的问题是,当我转到/ profile并重新加载页面时,$ soundcloud.fetch()返回的数据不可用,除非我将$ soundcloud.fetch()包含在{{1显然不理想的功能,所以我想知道我应该在setTimeout()
放置哪些内容,以便在页面加载时立即提供数据。
这是我的控制器目前:
$soundcloud.set()
如果我在setTimeout之外取app.controller('profileController', ['$scope', '$user', '$soundcloud', function ($scope, $user, $soundcloud) {
$scope.activeUser = $user.fetch();
$scope.activeSoundcloud = $soundcloud.fetch();
console.log($user.fetch());
setTimeout(function () {
console.log($soundcloud.fetch());
},100);
}]);
,那么我得到$soundcloud.fetch()
作为结果。应该在哪里调用$ soundcloud.set / fetch以使两组数据(用户和声音云)立即可用?
答案 0 :(得分:1)
<强> PLUNKER DEMO 强>
解决此问题的一种方法是获取所有必需的数据,然后手动引导应用程序。
为此,请创建bootstrapper.js
。您可以访问内置的角度服务,而无需通过angular.injector(['ng']).invoke()
引导模块,该模块接受具有可注入的依赖项的函数。 urlMap
变量只是设置的键值存储及其各自的URL。您通过urlMap
循环此angular.forEach()
,然后逐个获取所有设置,并将每个设置与每个相应的键一起存储在settings
变量中。将每个$http
承诺请求存储在promises
数组中,以$q.all()
解析它。当所有承诺都已解决后,调用bootstrap()
函数,在Settings
模块中添加app.settings
值,然后手动引导应用程序。
<强> bootstrapper.js 强>
angular.injector(['ng']).invoke(function($http, $q) {
var urlMap = {
$user: '/users/active/profile',
$soundcloud: '/users/active/services/soundcloud'
};
var settings = {};
var promises = [];
var appConfig = angular.module('app.settings', []);
angular.forEach(urlMap, function(url, key) {
promises.push($http.get(url).success(function(data) {
settings[key] = data;
}));
});
$q.all(promises).then(function() {
bootstrap(settings);
}).catch(function() {
bootstrap();
});
function bootstrap(settings) {
appConfig.value('Settings', settings);
angular.element(document).ready(function() {
angular.bootstrap(document, ['app', 'app.settings']);
});
}
});
<强> app.js 强>
angular.module('app', [])
.run(function(Settings) {
if(Settings === null) {
// go to login page or
// a page that will display an
// error if any of the requested
// settings failed
}
})
.controller('ProfileController', function($scope, Settings) {
console.log(Settings);
});
答案 1 :(得分:0)
$ soundcloud.set()的结果会覆盖sc的值。
如果在返回$ soundcloud.set()之前尝试将$ soundcloud.fetch中的值绑定到作用域,则会将undefined绑定到作用域,因为var sc尚未定义。当$ soundcloud.set()设置sc字段时,范围变量将永远不会得到通知,因为它只获得未定义的值并且对sc一无所知。
最简单的解决方法是创建一个永远不会被删除的包装器对象。这样的事情。
app.service('$soundcloud', ['$http', function ($http) {
var sc = {}; // define an object instead of undefined
this.set = function () {
$http.get('/users/active/services/soundcloud')
.success(function (obj) {
sc.soundcloud = obj; // write to a field on the object,
// do not override sc, since sc is already returned
})
.error(function () {
console.log('Error fetching soundcloud feed')
})
}
this.fetch = function () {
return sc;
}
}]);
当您使用$ soundcloud.fetch()的结果时,您需要参考soundcloud字段。至少在调用$ soundcloud.set时会始终更新
答案 2 :(得分:0)
你对异步性的问题是正确的。
.run()
中的代码会触发异步.get()
。.success()
的回调,您运行fetch()
。因此,fetch()
会返回未定义的sc
。console.log()
这个,因此你看到未定义。.get()
中的数据解析承诺,并将sc
分配给结果。 @Andre Paap 刚刚概述的方法很好 - 主要是通过创建一个空白的引用对象并在回调中更新其中一个属性(而不是完全覆盖引用),你可以仍然将它分配给范围,并且角度将在填充后(使用数据绑定)“神奇地”更新视图。 然而,如果您立即console.log()
结果,您仍会看到同样的事情。
换句话说,如果你在服务中有这个:
var currentUser = {};
this.set = function () {
return $http.get('/users/active/profile')
.success(function (obj) {
currentUser.name = obj.name;
})
.error(function () {
console.log('ERR');
})
}
这在您的模板中:
<span ng-show="currentUser.name"> Hello {{currentUser.name}}! </span>
然后将其放入您的控制器会导致上述范围在set()
调用返回后正确显示:
$scope.currentUser = $user.fetch()
// NOTE: the below would still log undefined:
console.log($scope.currentUser.name) //<- undefined.
如果您只想在返回.set()
调用后运行某些代码,那么您可以利用$http
方法返回promise的事实,如下所示:
$user.set().then(function(){
// This code runs *after* the call has returned.
$scope.currentUser = $user.fetch()
console.log($scope.currentUser.name) //<- "Ed"
});