I want to know if it is possible to make a service call that uses $http
so it returns data directly without returning a promise? I have tried to use the $q
and defer without any luck.
Here is what I mean:
I have a service:
angular.module('myModule').factor('myService', ['$http', '$q',
function($http, $q) {
// Public API
return {
myServiceCall: function() {
return $http.get('/server/call');
}
};
}
]);
And this is how I would call it:
// My controller:
myService.myServiceCall().then(function(data) {
$scope.data = data;
});
I want to avoid that and instead wish to have:
$scope.data = myService.myServiceCall();
I want it to be fully resolved at that line, is it possible?
I have tried some combinations of $q, defer and 'then' methods but can't seem to get it right since the method returns immediately.
Edit:
If you are wondering why, the main reason is that I wanted to simplify the controller code, this is easily done with ngResource as those calls are automatically resolved in templates, so I wanted to avoid the need to do the the whole '.then' everytime.
It's not that I don't like the Async nature, most of our code leverages it, it's just in some cases, having a synchronous way is helpful.
I think for now as some of you have pointed out, I will use the $resource for a near enough solution.
There are two ways I can think of to do this. The first is definitely better than the second.
resolve
property of routesYou can use the resolve
property of the route configuration object to specify that a promise must be resolved and the resolved value be used for the value of one of your controller dependencies, so that as soon as your controller runs, the value is immediately available.
For example, say you have the following service:
app.service('things', ['$http', '$q', function ($http, $q) {
var deferred = $q.defer();
$http({
method: 'GET',
url: '/things',
cache: true
}).success(function (data) {
deferred.resolve(data);
}).error(function (msg) {
deferred.reject(msg);
});
return deferred.promise;
}]);
Then you could define your route something like this:
$routeProvider.when('/things', {
controller: ['$scope', 'thingsData', function ($scope, thingsData) {
$scope.allThings = thingsData;
}],
resolve: {
thingsData: ['things', function (things) {
return things;
}]
}
});
The key in the resolve
object corresponds to the name of the second dependency for the controller, so even though it returns a promise, that promise will be resolved before the controller is run.
The second way, which is not at all ideal, and will likely bite you on the bum at some point, is to make the request for the data early on, such as when your app is initialised, and then actually get the data later on, say on a page that requires 3 clicks to get to from the home page. Fingers crossed, if the stars have aligned, your http request will have returned by then and you will have the data to use.
angular.module('myModule').factory('myService', ['$http', '$q', function($http, $q) {
var data = [];
function fetch() {
$http.get('/server/call').then(function (d) { data = d; });
}
// Public API
return {
fetchData: fetch,
myServiceCall: function () {
return data;
}
};
}]);
Then, in your module's run function, make the request:
angular.module('myModule').run(['myService', function (myService) {
myService.fetchData();
});
Then, in a controller for a page that is guaranteed to run at least 3 seconds after the app starts, you could do as you suggested:
angular.module('myModule').controller('deepPageCtrl', ['$scope', 'myService', function ($scope, myService) {
$scope.data = myService.myServiceCall();
if (angular.isArray($scope.data) && $scope.data.length === 0) {
panic("data isn't loaded yet");
}
}]);
Do as ricick suggests, and use the synchronous AJAX request possible with jQuery's .ajax()
method.