AngularJS $http call in a Service, return resolved data, not promises

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.

Answers:

Answer

There are two ways I can think of to do this. The first is definitely better than the second.

The first way: using the resolve property of routes

You 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: Make the request well before you need the data

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");
      }
  }]);

Bonus, equally bad third way: Synchronous AJAX request

Do as ricick suggests, and use the synchronous AJAX request possible with jQuery's .ajax() method.

Answer

This is not possible, as http calls are inherently asynchronous. That is, you need to wait for the server to respond.

The $resource service kind of gets around this by returning a proxy object that is populated with the returned data on server response, but it's still not fully resolved in one line.

Edit:

It is actually possible to make synchronous http requests with the jquery ajax method, by setting the "async" option to false.

You could create a service that wraps this, however your application will "lock up" while the request waits for a server response, so for most cases this would be bad practice. There are also other restrictions on this type of request.

See the docs for more detail:

http://api.jquery.com/jQuery.ajax/

Answer

Long time ago (in angular land, actually sometime last year) it was actually how it works.

But now it's no more the case, I think it's due to behavior differences of promise in view and controller, more here about the deprecation : Angularjs promise not binding to template in 1.2

Tags

Recent Questions

Top Questions

Home Tags Terms of Service Privacy Policy DMCA Contact Us

©2020 All rights reserved.