How does the $resource `get` function work synchronously in AngularJS?

I was watching this AngularJS tutorial describing how to hook into Twitter with an Angular resource. (Video tutorial) Here is the resource that is set up in the example controller:

$scope.twitter = $resource('http://twitter.com/:action',
    {action: 'search.json', q: 'angularjs', callback: 'JSON_CALLBACK'},
    {get: {method: 'JSONP'}});

The tutorial shows that there are a couple ways to get data back from the resource using the get call. The first method is by passing a callback to the get function. The callback will be called with the result after the ajax request returns:

$scope.twitter.get(function(result) {
    console.log('This was the result:', result);
});

I understand this method. It makes perfect sense to me. The resource represents a place on the web where you can get data, and get simply makes an ajax call to a url, gets json back, and calls the callback function with the json. The result param is that json.

It makes sense to me because it seems obvious that this is an asynchronous call. That is, under the hood, the ajax call fires, and the code following the call isn't blocked, it continues to be executed. Then at some indeterminate point later on, when the xhr is successful, the callback function is called.

Then the tutorial shows a different method that looks a lot simpler, but I don't understand how it works:

$scope.twitterResult = $scope.twitter.get();

I assume that the xhr underneath get must be asynchronous, yet in this line we are assigning the return value of the get call to a variable, as if it returned synchronously.

Am I wrong for not understanding this? How is that possible? I think it's really neat that it works, I just don't get it.

I understand that get can return something while the xhr underneath it goes off and processes asynchronously, but if you follow the code example yourself, you will see that $scope.twitterResult gets the actual twitter content before any subsequent lines are executed. For example, if you write console.log($scope.twitterResult) immediately after that line, you will see the results from twitter logged in the console, not a temporary value that is replaced later on.

More importantly, because this is possible, how can I write an Angular service that takes advantage of this same functionality? Besides ajax requests, there are other types of data stores requiring asynchronous calls that can be used in JavaScript which I would love to be able to write code for synchronously in this style. For example, IndexedDB. If I could wrap my head around how Angular's built-in resources are doing it, I would give it a shot.

Answers:

Answer

$resource is not synchronous although this syntax might suggest that it is:

$scope.twitterResult = $scope.twitter.get();

What is going on here is that call to the AngularJS will, after call to twitter.get(), return immediately with the result being an empty array. Then, when the async call is finished and real data arrives from the server, the array will get updated with data. AngularJS will simply keep a reference to an array returned and will fill it in when data are available.

Here is the fragment of $resource implementation where the "magic" happens: https://github.com/angular/angular.js/blob/master/src/ngResource/resource.js#L372

This is described in the $resource documentation as well:

It is important to realize that invoking a $resource object method immediately returns an empty reference (object or array depending on isArray). Once the data is returned from the server the existing reference is populated with the actual data. This is a useful trick since usually the resource is assigned to a model which is then rendered by the view. Having an empty object results in no rendering, once the data arrives from the server then the object is populated with the data and the view automatically re-renders itself showing the new data. This means that in most case one never has to write a callback function for the action methods.

Answer

$q can do this trick too. You can convert a normal object to a 'delayed value' using something like this:

var delayedValue = function($scope, deferred, value) {
    setTimeout(function() {
        $scope.$apply(function () {
            deferred.resolve(value);
        });
    }, 1000);
    return deferred.promise;
};

and then use it in a controller, to get a similar effect to what $scope.twitter.get() does in the OP's example

angular.module('someApp', [])
.controller('someController', ['$scope', '$q', function($scope, $q) {
  var deferred = $q.defer();
  $scope.numbers = delayedValue($scope, deferred, ['some', 'numbers']);
}]);

Tags

Recent Questions

Top Questions

Home Tags Terms of Service Privacy Policy DMCA Contact Us

©2020 All rights reserved.