andimeier
andimeier

Reputation: 1263

Angularjs: how can I make a service code "look synchronous"?

How can I make an Angular service code "look synchronous"?

My questions arose when I cleaned my controller and put the business logic code into a service instead. So far so good. Now I would like to "wait" in the service function until all asynchronous calls have returned and then return. How can I do that?

To illustrate my problem, suppose you have a controller code which just:

  1. requests some data from the backend
  2. does some processing with the data and
  3. hands the data over to the scope

Like that:

DataController before refactoring:

$scope.submitForm = function() {

    RestBackend.query('something').then(function(data) {

        // do some additional things ...
        ...

        $scope.data = data;
    });
};

Pretty straightforward. Fetch data and fill scope.

After refactoring into controller + service, I ended up with:

DataController refactored:

$scope.submitForm = function() {

    DataService.getData().then(function(data) {
        $scope.data = data;
    });
};

DataService refactored:

this.query = function() {
    var dataDefer = $q.defer();

    RestBackend.query('something').then(function(data) {

        // do some additional things ...
        ...

        dataDefer.resolve(data);
    });
    return dataDefer.promise;
};

I dislike the fact that I have to work with a promise in the controller also. 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 get the point? In the controller I don't want to care about promise or not. Just wait for the data to be fetched and then use it. Thus, I am looking for a possibility to implement the service like this:

  1. query the data (asynchronously)
  2. do not return until the data has been fetched
  3. return the fetched data

Now item 2. is not clear to me: How can I "wait until data has been fetched" and only proceed afterwards? The goal is that the service function looks synchronous.

Upvotes: 3

Views: 970

Answers (3)

meriadec
meriadec

Reputation: 2983

As you use ngRoute, I would recommend you to resolve you data in your route config, and the view will be loaded once all your data will be resolved.

$routeProvider
  .when('/your-url', {
    templateUrl: 'path/to/your/template.html',
    controller: 'YourCtrl',

    // that's the point !
    resolve: {
      superAwesomeData: function (DataService) {
        return DataService.getData();
      }
    }
  });

Now, superAwesomeData can be injected in your controller and it will contains the data, resolved.

angular.module('youModule')
  .controller('YourCtrl', function (superAwesomeData) {

    // superAwesomeData === [...];

  });

Upvotes: 1

gkalpak
gkalpak

Reputation: 48211

I too think your solution is fine.
Returning a promise is not an implementation detail of the service. It is part of the service's API (the "contract" between the service and the service-consumer).

The controller expects a promise that resolves with the data and handles that as it sees fit.
How that promise is constructed, how the data is fetched etc, these are the implementation details.
You can swap the service at any time with one that does totally different things as long as it fulfills the contract (i.e. returns a promise that resolves with the data onve ready).


That said, if you only use the data in the view (i.e. do not directly manipulate it in the controller right after it is fetched), which seems to be the case, you can use ngResources approach:

Return an empty array and populate it with the data once it is fetched:

$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;
};

BTW, in your current (fine) setup, you need $q.defer(). You can just use promise-chaining:

this.query = function() {
    return RestBackend.query('something').then(function(data) {
        // do some additional things ...
        ...
        return data;
    });
};

Upvotes: 2

Tim
Tim

Reputation: 2715

I think what you have is a very good solution. You should not have to wait for promise to be resolved, it defeats the purpose of async javascript. Just ask yourself why do you need to make it run sync?

If you rely in html on promise to be resolve you can do something like this

<div class="alert alert-warning text-center" data-ng-hide="!data.$resolved">
   Got data from service.
</div>

Upvotes: 1

Related Questions