Reputation: 207
I've been trying to make a pretty and user friendly app in angular and i'm at the part where i let the user know stuff is loading when i make ajax requests. What i'm trying to accomplish : i want to create a service that handles the initialization data fetching and pass the data as an object to my controller when it becomes available. Of course, the view isn't displayed until all the initial data my controller needs is available. Here's what i have so far:
app.js
var Application = angular.module('ReporterApplication', ['ngRoute']);
Application.service('Initialize', ['$http', '$q', '$timeout', function($http, $q, $timeout)
{
this.serverData = function()
{
$http.get('/packing/scan.action')
.success(function(data)
{
return data;
})
.error(function(data)
{
return data;
});
};
}])
Application.config(['$routeProvider', '$interpolateProvider',
function($routeProvider, $interpolateProvider) {
$interpolateProvider.startSymbol('<%');
$interpolateProvider.endSymbol('%>');
$routeProvider
.when('/packing/scan.html', {
templateUrl: 'packing/scan.html',
controller: 'PackingScanController as packer',
resolve: {
initData : 'Initialize' .... etc more code
scan.js
Application.controller('PackingScanController', ['$scope', '$http', 'initData', function($scope, $http, initData) {
var packer = this;
packer.packedToday = initData.serverData(); ... etc more code
Upvotes: 0
Views: 2814
Reputation: 1370
All these errors come from the fact that we’re trying to transform an asynchronous call into a synchronous one. That is simply not possible.
serverData() doesn’t return anything anymore. The return data statement is not returning from serverData(). It’s returning from the anonymous callback function passed to then().
The anonymous callback is not called immediately. It’s called when the response is available. And that happens long after serverData() has returned.
So, 1. one way: use callbacks
Application.service('initdata',function($http){
var serverData = function(callback){
$http.get('/packing/scan.action').then(function(response){
callback(response.data);
});
};
return {
serverData: serverData
};
});
Application.controller('PackingScanController',function(initdata){
initdata.serverData(function(serverdata){
$scope.serverdata = serverdata;
});
});
2. Second way simply return the HTTP promise, and let the controller deal with it.
Application.service('initdata',function($http){
var serverData = function(){
return $http.get('/packing/scan.action');
};
return {
serverData: serverData
};
});
Application.controller('PackingScanController',function(initdata){
initdata.serverData().then(function(response){
$scope.serverdata = response.data;
});
});
3.You can also return serverData promise instead of returning http response promise.
Application.service('initdata',function($http){
var serverData = function() {
var defer = $q.defer();
$http.get('/api/ponies').then(function(response) {
defer.resolve(response.data);
});
return defer.promise;
};
return {
serverData: serverData
};
});
Application.controller('PackingScanController',function(initdata){
initdata.serverData().then(function(response){
$scope.serverdata = response.data;
});
});
Upvotes: 0
Reputation: 54649
If you want your controller to wait, what you need to do is to configure the resolve
option correctly. For this, you need to have your service return a promise, otherwise the router will not wait.
resolve - {Object.<string, function>=}
- An optional map of dependencies which should be injected into the controller. If any of these dependencies are promises, the router will wait for them all to be resolved or one to be rejected before the controller is instantiated.
Here's a simplified example:
app.service('Initialize', ['$http', function($http) {
this.serverData = function()
{
// return the http promise
return $http
.get('config.json')
// use success/error if needed
.then(function (response) {
// make sure, only the response data is used
return response.data;
})
;
};
}]);
app.config(['$routeProvider', function($routeProvider) {
$routeProvider
.when('/', {
template: '<pre>{{ main.initData|json }}</pre>',
controller: 'MainCtrl as main',
resolve: {
// configure with DI
initData: ['Initialize', function (Initialize) {
// call the service method, and return the promise
return Initialize.serverData();
}]
}
})
;
}]);
app.controller('MainCtrl', function (initData) {
// initData will be loaded
this.initData = initData;
});
demo: http://plnkr.co/edit/sOwyHz4yMvwujBWYzOn0?p=preview
Upvotes: 1
Reputation: 7179
You can inject your service in any controllers so you don't need to pass it via resolve.
Application.controller('PackingScanController', ['$scope', '$http', 'Initialize', function($scope, $http, Initialize)
For loader normally what I do is I check the availability of the data in template using ng-show
<div ng-show="!data">Loading...</div>
<div ng-show="!!data" ng-repeat="d in data">...</div>
Any time when you need the loader to show, just set your $scope.data to null and proceed to call your API service.
By the way your service won't work without promise as suggested by other user in your comment.
Upvotes: 0