Angularjs: how can I make my maintenance code "watch synchronously"?
How do I make my Angular service code "synchronous"?
My questions came up when I cleaned up the controller and put the business logic code into the service. So far, so good. Now I would like to "wait" in the service until all asynchronous calls have returned, and then they will return. How can i do this?
To illustrate my problem, let's say you have controller code that is simply:
- requests some data from the backend
- does some processing on the data and
- transfers data to area
Like:
DataController before refactoring:
$scope.submitForm = function() {
RestBackend.query('something').then(function(data) {
// do some additional things ...
...
$scope.data = data;
});
};
Pretty simple. Extract data and fill area.
After refactoring to controller + service, I ended up with:
DataController refactored:
$scope.submitForm = function() {
DataService.getData().then(function(data) {
$scope.data = data;
});
};
Refactoring the DataService:
this.query = function() {
var dataDefer = $q.defer();
RestBackend.query('something').then(function(data) {
// do some additional things ...
...
dataDefer.resolve(data);
});
return dataDefer.promise;
};
I don't like the fact that I have to work with a promise in a controller. I like promises, but I want to keep the controller agnostic of this "implementation detail" of the service. This is what I would like the controller code to look like:
DataController (as it should be):
$scope.submitForm = function() {
$scope.data = DataService.getData();
};
You understand? In the controller, I don't want to care about the promise or not. Just wait for the data to be retrieved and then use it. Thus, I am looking for an opportunity to implement a service like this:
- request data (asynchronously)
- not returned until data has been retrieved
- return the retrieved data
Now it is not clear to me what point 2. How can I "wait until the data is retrieved" and only after that? The goal is for the service function to appear synchronous.
source to share
I think you have a very good solution. You don't have to wait for the promise to resolve, it defeats the purpose of asynchronous javascript. Just ask yourself why you need to start syncing?
If you are relying on html for the promise of being decisive you can do something like this
<div class="alert alert-warning text-center" data-ng-hide="!data.$resolved">
Got data from service.
</div>
source to share
I also think your solution is ok.
Returning a promise is not a detail of the implementation of this service. It is part of the service API (the "contract" between the service and the service consumer).
The controller expects a promise that permits the data and processes it as it sees fit.
How this promise is made, how the data is retrieved, etc. are the implementation details.
You can trade a service at any time with something that does completely different things as long as it fulfills a contract (i.e. returns a promise that is readily resolved).
That said, if you are only using the data in the view (i.e., not manipulating it directly in the controller immediately after you receive it), then it appears you can use the approach ngResources
:
Returns an empty array and populates it with data after receiving it:
$scope.data = DataService.getData();
// DataService refactored:
this.getData = function () {
var data = [];
RestBackend.query('something').then(function(responseData) {
// do some additional things ...
...
angular.forEach(responseData, function (item) {
data.push(item);
});
});
return data;
};
By the way, in your current (tweak) setting you need $q.defer()
. You can just use the merge chain:
this.query = function() {
return RestBackend.query('something').then(function(data) {
// do some additional things ...
...
return data;
});
};
source to share
As you use ngRoute
, I would recommend that you allow your data in the route config and the view will be loaded after all your data is resolved.
$routeProvider
.when('/your-url', {
templateUrl: 'path/to/your/template.html',
controller: 'YourCtrl',
// that the point !
resolve: {
superAwesomeData: function (DataService) {
return DataService.getData();
}
}
});
superAwesomeData
Can now be injected into your controller and it will contain the data allowed.
angular.module('youModule')
.controller('YourCtrl', function (superAwesomeData) {
// superAwesomeData === [...];
});
source to share