133

Is there any way to make a synchronous call with AngularJS?

The AngularJS documentation is not very explicit or extensive for figuring out some basic stuff.

ON A SERVICE:

myService.getByID = function (id) {
    var retval = null;

    $http({
        url: "/CO/api/products/" + id,
        method: "GET"
    }).success(function (data, status, headers, config) {

        retval = data.Data;

    });

    return retval;
}
Cœur
  • 37,241
  • 25
  • 195
  • 267
Flavio CF Oliveira
  • 5,235
  • 13
  • 40
  • 63
  • See also https://groups.google.com/d/topic/angular/qagzXXhS_VI/discussion for some ideas about how to deal with the asynchronous behavior: events, $watch, preload on the server side, use the promise returned from $http. – Mark Rajcok Oct 26 '12 at 18:58
  • 1
    Asynchronous is always better, especially when you have promises. – Andrew Joslin Feb 09 '13 at 16:49
  • Many times, you can avoid synchronous calls. See how $resource works http://stackoverflow.com/questions/11966252/how-does-the-resource-get-function-work-synchronously-in-angularjs. – honzajde Feb 25 '13 at 19:20
  • 4
    @AndrewJoslin Asynchronous is worse when you need ordered delivery. – Stijn Van Antwerpen Apr 12 '16 at 11:30

7 Answers7

115

Not currently. If you look at the source code (from this point in time Oct 2012), you'll see that the call to XHR open is actually hard-coded to be asynchronous (the third parameter is true):

 xhr.open(method, url, true);

You'd need to write your own service that did synchronous calls. Generally that's not something you'll usually want to do because of the nature of JavaScript execution you'll end up blocking everything else.

... but.. if blocking everything else is actually desired, maybe you should look into promises and the $q service. It allows you to wait until a set of asynchronous actions are done, and then execute something once they're all complete. I don't know what your use case is, but that might be worth a look.

Outside of that, if you're going to roll your own, more information about how to make synchronous and asynchronous ajax calls can be found here.

I hope that is helpful.

Witold Kaczurba
  • 9,845
  • 3
  • 58
  • 67
Ben Lesh
  • 107,825
  • 47
  • 247
  • 232
  • 13
    Can you please code snippet to achieve using $q service. I tried many options but it is working in an asynchronous manner. – Venkat Jan 29 '14 at 03:45
  • 1
    There are places where it can make sense, e.g. just when the user closes the browser (onbeforeunload), if you want to save you have to send a sync request, another option is to show a dialog cancel, and then relaunch the window close? – Braulio May 19 '14 at 14:07
  • 2
    @Venkat: I know this is a late reply, but as I said in the answer, the call will always be "asynchronous" you just have to use $q to make it *wait* for the the response, then continue your logic inside of the `.then(callback)`. something like: `doSomething(); $http.get('/a/thing').then(doEverythingElse);`. – Ben Lesh May 20 '14 at 21:30
  • 3
    The following video helped me in studying promises [AngularJS Promises with $q](https://www.youtube.com/watch?v=cdG_T6ufcbE) – Ilya Palkin Jun 23 '14 at 10:10
  • @circuitry This answer needs a code example of what? The question was, specifically, how to make `$http` behave synchronously in Oct. 2012. At the time that wasn't possible. So a code example would have been like `throw new Error('you cannot do this')`. – Ben Lesh Apr 14 '16 at 19:37
  • @BenLesh, When I come here I'm looking for solutions. You said "maybe you should look into promises and the $q service." What would that look like? Also if the answer is out of date you could update it. Your post did not provide a code example of a workaround, which is what I was looking for. – circuitry Apr 14 '16 at 21:29
  • @circuitry it's a free answer on a free site given with time I volunteered. 82 people found it useful. Sorry you didn't. Feel free to update the answer yourself, if you like. I honestly would, but I don't have the time. – Ben Lesh Apr 20 '16 at 23:02
  • 1
    @BenLesh I'm not unappreciative of the time you put in, or the time anyone puts in. I'm free to down-vote your answer and say that it would have been helpful to me if an example was provided. I saw your answer, it didn't help me, so I down voted it and closed the tab, and went back to google to try to find an answer that was more helpful to me. It's not the end of the world when someone down-votes your answer, and tells you how it could be improved. Would you have preferred I down-voted without leaving a comment as to why? Just being honest. – circuitry Apr 21 '16 at 16:34
  • 1
    Also just because you spent time on the answer doesn't make it any more helpful. Here is an example that was helpful to me: http://stackoverflow.com/questions/16227644/angularjs-factory-http-service – circuitry Apr 21 '16 at 16:39
  • I really don't care about your downvote. Or the votes. I rarely answer anything on SO anymore. "When I come here I'm looking for solutions" "if the answer is out of date you could update it". It reads like entitlement to me. Perhaps that's a misunderstanding, but it wouldn't be the first time I've seen that on SO. – Ben Lesh Apr 21 '16 at 20:21
12

I have worked with a factory integrated with google maps autocomplete and promises made​​, I hope you serve.

http://jsfiddle.net/the_pianist2/vL9nkfe3/1/

you only need to replace the autocompleteService by this request with $ http incuida being before the factory.

app.factory('Autocomplete', function($q, $http) {

and $ http request with

 var deferred = $q.defer();
 $http.get('urlExample').
success(function(data, status, headers, config) {
     deferred.resolve(data);
}).
error(function(data, status, headers, config) {
     deferred.reject(status);
});
 return deferred.promise;

<div ng-app="myApp">
  <div ng-controller="myController">
  <input type="text" ng-model="search"></input>
  <div class="bs-example">
     <table class="table" >
        <thead>
           <tr>
              <th>#</th>
              <th>Description</th>
           </tr>
        </thead>
        <tbody>
           <tr ng-repeat="direction in directions">
              <td>{{$index}}</td>
              <td>{{direction.description}}</td>
           </tr>
        </tbody>
     </table>
  </div>

'use strict';
 var app = angular.module('myApp', []);

  app.factory('Autocomplete', function($q) {
    var get = function(search) {
    var deferred = $q.defer();
    var autocompleteService = new google.maps.places.AutocompleteService();
    autocompleteService.getPlacePredictions({
        input: search,
        types: ['geocode'],
        componentRestrictions: {
            country: 'ES'
        }
    }, function(predictions, status) {
        if (status == google.maps.places.PlacesServiceStatus.OK) {
            deferred.resolve(predictions);
        } else {
            deferred.reject(status);
        }
    });
    return deferred.promise;
};

return {
    get: get
};
});

app.controller('myController', function($scope, Autocomplete) {
$scope.$watch('search', function(newValue, oldValue) {
    var promesa = Autocomplete.get(newValue);
    promesa.then(function(value) {
        $scope.directions = value;
    }, function(reason) {
        $scope.error = reason;
    });
 });

});

the question itself is to be made on:

deferred.resolve(varResult); 

when you have done well and the request:

deferred.reject(error); 

when there is an error, and then:

return deferred.promise;
allel
  • 855
  • 1
  • 12
  • 22
5
var EmployeeController = ["$scope", "EmployeeService",
        function ($scope, EmployeeService) {
            $scope.Employee = {};
            $scope.Save = function (Employee) {                
                if ($scope.EmployeeForm.$valid) {
                    EmployeeService
                        .Save(Employee)
                        .then(function (response) {
                            if (response.HasError) {
                                $scope.HasError = response.HasError;
                                $scope.ErrorMessage = response.ResponseMessage;
                            } else {

                            }
                        })
                        .catch(function (response) {

                        });
                }
            }
        }]


var EmployeeService = ["$http", "$q",
            function ($http, $q) {
                var self = this;

                self.Save = function (employee) {
                    var deferred = $q.defer();                
                    $http
                        .post("/api/EmployeeApi/Create", angular.toJson(employee))
                        .success(function (response, status, headers, config) {
                            deferred.resolve(response, status, headers, config);
                        })
                        .error(function (response, status, headers, config) {
                            deferred.reject(response, status, headers, config);
                        });

                    return deferred.promise;
                };
Srinivas
  • 22,589
  • 1
  • 14
  • 4
4

I recently ran into a situation where I wanted to make to $http calls triggered by a page reload. The solution I went with:

  1. Encapsulate the two calls into functions
  2. Pass the second $http call as a callback into the second function
  3. Call the second function in apon .success
2

Here's a way you can do it asynchronously and manage things like you would normally. Everything is still shared. You get a reference to the object that you want updated. Whenever you update that in your service, it gets updated globally without having to watch or return a promise. This is really nice because you can update the underlying object from within the service without ever having to rebind. Using Angular the way it's meant to be used. I think it's probably a bad idea to make $http.get/post synchronous. You'll get a noticeable delay in the script.

app.factory('AssessmentSettingsService', ['$http', function($http) {
    //assessment is what I want to keep updating
    var settings = { assessment: null };

    return {
        getSettings: function () {
             //return settings so I can keep updating assessment and the
             //reference to settings will stay in tact
             return settings;
        },
        updateAssessment: function () {
            $http.get('/assessment/api/get/' + scan.assessmentId).success(function(response) {
                //I don't have to return a thing.  I just set the object.
                settings.assessment = response;
            });
        }
    };
}]);

    ...
        controller: ['$scope', '$http', 'AssessmentSettingsService', function ($scope, as) {
            $scope.settings = as.getSettings();
            //Look.  I can even update after I've already grabbed the object
            as.updateAssessment();

And somewhere in a view:

<h1>{{settings.assessment.title}}</h1>
Bluebaron
  • 2,289
  • 2
  • 27
  • 37
0

Since sync XHR is being deprecated, it's best not to rely on that. If you need to do a sync POST request, you can use the following helpers inside of a service to simulate a form post.

It works by creating a form with hidden inputs which is posted to the specified URL.

//Helper to create a hidden input
function createInput(name, value) {
  return angular
    .element('<input/>')
    .attr('type', 'hidden')
    .attr('name', name)
    .val(value);
}

//Post data
function post(url, data, params) {

    //Ensure data and params are an object
    data = data || {};
    params = params || {};

    //Serialize params
    const serialized = $httpParamSerializer(params);
    const query = serialized ? `?${serialized}` : '';

    //Create form
    const $form = angular
        .element('<form/>')
        .attr('action', `${url}${query}`)
        .attr('enctype', 'application/x-www-form-urlencoded')
        .attr('method', 'post');

    //Create hidden input data
    for (const key in data) {
        if (data.hasOwnProperty(key)) {
            const value = data[key];
            if (Array.isArray(value)) {
                for (const val of value) {
                    const $input = createInput(`${key}[]`, val);
                    $form.append($input);
                }
            }
            else {
                const $input = createInput(key, value);
                $form.append($input);
            }
        }
    }

    //Append form to body and submit
    angular.element(document).find('body').append($form);
    $form[0].submit();
    $form.remove();
}

Modify as required for your needs.

Adam Reis
  • 4,165
  • 1
  • 44
  • 35
-4

What about wrapping your call in a Promise.all() method i.e.

Promise.all([$http.get(url).then(function(result){....}, function(error){....}])

According to MDN

Promise.all waits for all fulfillments (or the first rejection)

Ovidiu Dolha
  • 5,335
  • 1
  • 21
  • 30
  • what are you talking about? the question has nothing to do with multiple promises... – Ovidiu Dolha May 24 '17 at 13:33
  • It will wait for one or more promise to complete! – Manjit Dosanjh May 24 '17 at 13:37
  • have you used this to see how it works? Promise.all will return another promise, it does not transform async into sync calls – Ovidiu Dolha May 24 '17 at 13:38
  • Hmm... it seems that the MDN documentation may be ambiguous... It does not actually WAIT as stated in their documentation. – Manjit Dosanjh May 24 '17 at 13:42
  • Welcome to SO. Please read this [how-to-answer](http://stackoverflow.com/help/how-to-answer) for providing quality answer. – thewaywewere May 24 '17 at 13:45
  • I wouldn't say it's ambiguous because `waits for all fulfillments` is not the same thing as synchronous code in JS. It waits for fulfillment in the sense that you can use it to make sure a number of X promises all finish before you get the results and can do something else. – Ovidiu Dolha May 24 '17 at 13:45
  • You are right. Subtle but important difference. Chaining is not the same as synchrony although one could be an awkward way of implementing the other. For that reason I will forebear to suggest a way of getting the correct order of execution using promises. There must be a simpler way. – Manjit Dosanjh May 24 '17 at 14:10
  • As the OP asked, he wants a sync call to make another maybe to use its data next, using promise all we will get a concurrence here event waiting all to get its result. – Rodrigo Dec 19 '18 at 15:34