Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.

like image 900
iQ. Avatar asked Dec 01 '14 22:12

iQ.


People also ask

What is then() in Angular?

The then() method takes two callback functions as parameters and is invoked when a promise is either resolved or rejected. The catch() method takes one callback function and is invoked when an error occurs.

What is $HTTP service in AngularJS?

The $http service is a core AngularJS service that facilitates communication with the remote HTTP servers via the browser's XMLHttpRequest object or via JSONP. For unit testing applications that use $http service, see $httpBackend mock. For a higher level of abstraction, please check out the $resource service.

What is defer promise in AngularJS?

defer() to create a Promise. A Promise is a function that returns a single value or error in the future. So whenever you have some asynchronous process that should return a value or an error, you can use $q. defer() to create a new Promise.

What is http Angular?

Angular provides a client HTTP API for Angular applications, the HttpClient service class in @angular/common/http . The HTTP client service offers the following major features. The ability to request typed response objects. Streamlined error handling. Testability features.


2 Answers

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.

like image 60
GregL Avatar answered Nov 15 '22 15:11

GregL


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/

like image 33
ricick Avatar answered Nov 15 '22 15:11

ricick