Code runs in a different order than expected - angularjs

JS
<script type="application/javascript">
var app = angular.module("app", []);
app.controller("AppCtrl", function ($scope, $http) {
$scope.data = [];
$http.get("{{ url_for('data') }}")
.then(function (result) {
$scope.data = result.data;
console.log(result.data); //first
});
console.log($scope.data); //second
...
</script>
first console.log returns the correct data (Array with one item), the second one returns empty Array.
Notice that the order of execution is not as the code:
Console
Array [ ]
Array [ "data" ]
Why the second console.log executes before the first one, How can it be fixed?

Because the http.get is asynchronous i.e. it goes off and does something on another thread, whilst the rest of the program continues. By the time it's finished, and done it's console.log, the '2nd' console.log has already run.

The call to $http.get is asynchronous so you don't know up-front how long it will take. console.log($scope.data) will be executed immediately following the $http.get request because the request will still be processing and waiting to return.
If you want to execute logic after your requests complete, you can add additional logic inside of the your then() and then chain them if you require additional requests to be executed. For instance,
$http
.get("{{ url_for('data') }}")
.then(function (result) {
$scope.data = result.data;
// do more things
return $http.get('foo/bar')
}).then(function (fooBar) {
// $scope.foo = bar;
});
All $http calls return a promise so you're able to leverage the $q service to facilitate any promise specific functionality.

Angular's $http methods run asynchronously. This means the console.log($scope.data); will be excecuted first and the console.log(result.data) inside then will be excecuted when the promise from $http resolves.

$http is the service which will be performed asynchronously

This is because Angular's JavaScript runs asynchronously. This means that It will keep running even though it's waiting on another task. You should look up angular's guild on Promises.

Related

Angularjs scope length outside function

Im a newbie in AngularJs and do not really know how to get length of response outside function.
var onUsers = function(response){
$scope.users = response.data;
console.log($scope.users.length); //here works
}
But when i try outside onUsers function
console.log($scope.users.length);
I get an error.
I think the problem is that you are trying to access $scope.users.length before your response has returned from the server. so $scope.users is still undefined. This is a classic problem of asynchronous javascript and you need to use promises for this.
Which means that you should write the code that you are executing outside onUsers in the promise callback.
Read this: https://docs.angularjs.org/api/ng/service/$q
You can use $scope.$watch for run a function when the variable change.
$scope.$watch('users', function(users) {
console.log(users.length);
});
AS the angular code runs asynchronous so console.log($scope.users.length) runs before the promise returned.
var onUsers = function(response){
$scope.users = response.data;
console.log($scope.users.length); //here works
}
your console.log($scope.users.length) in above function works because it is a success handler.
you can get the your console.log works out side the success handler by using $timeout. i am assuming after 5000 your response will arive.
$timeout(function() {
console.log($scope.users.length);
}, 5000);

Order of execution of $http callbacks

It seems that factory methods execution priority is the highest, so that callbacks has no data to deal with. What is the best way to make this work?
I got this kind of factory
app.factory('jsonService', function($http) {
return {
getDistricts: function(callback) {
$http.get('data/districts.json').success(callback);
},
getLocations: function(path,callback) {
$http.get('data/'+path+'.json').success(callback);
}
};
});
And controller
var app = angular.module('sandbox', []);
app.controller('sandboxCtrl',function ($scope,jsonService) {
//This one works
$scope.init1= function(){
jsonService.getDistricts(function(data){
$scope.districts = data;
$scope.currentDistrict = $scope.districts[0].name;
jsonService.getLocations($scope.currentDistrict,function(data){
$scope.locations1 = data;
})
});
};
$scope.init1();
//This one does not
$scope.init2= function(){
jsonService.getDistricts(function(data){
$scope.districts = data;
$scope.currentDistrict = $scope.districts[0].name;
})
jsonService.getLocations($scope.currentDistrict,function(data){
$scope.locations1 = data;
});
};
$scope.init2();
});
Here is working plunker
Angular has an implementation of promises named $q (documentation) that you should read up upon.
There is a race condition due to the async nature of http calls. Please review the updated code linked to below that shows an example of your code running (successfully) using promises to handle your two calls in succession.
So upon success of your first call it will call your second service method all without using callbacks thanks to the power of promises.
jsonService.getDistricts()
.success(function(data) {
$scope.districts = data;
$scope.currentDistrict = $scope.districts[0].name;
jsonService.getLocations($scope.currentDistrict)
.success(function(locationData) {
$scope.locations = locationData;
})
});
updated PLKR
Promise clarification:
The raw implementation of basic promises uses then to handle responses and promises returned from $http add additional methods (success, error) that will unpack your data from the response object that you would need to handle if your just use then.
init1() is the correct way of doing this. init2() does work because jsonService.getLocations() is getting invoked before jsonService.getDistritcs() completes. The angular $http service is asynchronous. Since jsonService.getLocations() depends on data from jsonServicd.getDistricts() you must wait until .getDistricts() completes before calling .getLocations(). One way to do that is to call .getLocations() within the .getDitricts() callback, just as you did in init1().

Assign multiple $http return data to an array in the called order

$scope.iter = 0;
$scope.myArray.forEach(function () {
$http.get($scope.myArray[$scope.iter].URL)
.success(function (data) {
$scope.myArray2.push(data);
//$scope.myArray2[$scope.iter]=data
});
$scope.iter++;
})
The above code works but I want the results in myArray2 in the same order as it was called. I know that I cannot expect $scope.myArray2[$scope.iter]=data to work but that is what I need.
I looked at the angular documentation on promises but could not make out how to use it for the above.
You can put all promises from the get requests in an array and use $q.all() to create a promise that resolves when all underlying promises resolve. You can then iterate the responses in the order they were added to the requests array, and push each response's data into the array in order...
function controller ($scope, $q) {
// ...
var requests = [];
var $scope.myArray2 = [];
angular.forEach($scope.myArray, function (value) {
requests.push($http.get(value.URL));
});
$q.all(requests).then(function(results) {
angular.forEach(results, function(result) {
$scope.myArray2.push(result.data);
});
});
}
Dont understand what you are trying to achieve, but here is an example of simple deferred promises in a controller:
var firstDefer= $q.defer();
firstDefer.promise.then(function(thing){
// here you make the first request,
// only when the first request is completed
//the variable that you return will be filled and
//returned. The thing that you return in the first .then
// is the parameter that you receive in the second .then
return thingPlusRequestData;
}).then(function(thingPlusRequestData){
//second request
return thingPlusPlusRequestData;
}).then(function(thingPlusPlusRequestData){
//and so on...
});
firstDefer.resolve(thing);
//when you call .resolve it tries to "accomplish" the first promise
//you can pass something if you want as a parameter and it will be
// the first .then parameter.
Hope this helps u :D
You will normally NOT get the results in the order you called the $http.get(...) function. Mind that the success(...) function is called asynchronously, whenever the http response comes in, and the order of those responses is totaly unpredictable.
However you can work around this by waiting for all the responses to finish, and then sort them according to your criteria.
Here is the working fiddle: http://fiddle.jshell.net/3C8R3/3/

AngularJS: make data received with $http inside factory to be accessible inside controller

I have a factory called "Server" which contains my methods for interaction with the server (get/put/post/delete..). I managed to login and get all data successfully when I had all my code in my controller. Now that I want to separate this code and restructure it a little bit I ran into problems. I can still login and I also get data - but data is just printed; I'm not sure how to access the data in controller? I saw some ".then" instead of ".success" used here and there across the web, but I don't know how exactly.
This is my factory: (included in services.js)
app.factory('Server', ['$http', function($http) {
return {
// this works as it should, login works correctly
login: function(email,pass) {
return $http.get('mywebapiurl/server.php?email='+email+'&password='+pass').success(function(data) {
console.log("\nLOGIN RESPONSE: "+JSON.stringify(data));
if(data.Status !== "OK")
// login fail
console.log("Login FAIL...");
else
// success
console.log("Login OK...");
});
},
// intentional blank data parameter below (server configured this way for testing purposes)
getAllData: function() {
return $http.get('mywebapiurl/server.php?data=').success(function(data) {
console.log("\nDATA FROM SERVER: \n"+data); // here correct data in JSON string format are printed
});
},
};
}]);
This is my controller:
app.controller("MainController", ['$scope', 'Server', function($scope, Server){
Server.login(); // this logins correctly
$scope.data = Server.getAllData(); // here I want to get data returned by the server, now I get http object with all the methods etc etc.
…. continues …
How do I get data that was retrieved with $http within a factory to be accessible in controller? I only have one controller.
Thanks for any help, I'm sure there must be an easy way of doing this. Or am I perhaps taking a wrong way working this out?
EDIT: I also need to be able to call factory functions from views with ng-click for instance. Now I can do this like this:
// this is a method in controller
$scope.updateContacts = function(){
$http.get('mywebapiURL/server.php?mycontacts=').success(function(data) {
$scope.contacts = data;
});
};
and make a call in a view with ng-click="updateContacts()". See how $scope.contacts gets new data in the above function. How am I supposed to do this with .then method?(assigning returned data to variable)
My question asked straight-forwardly:
Lets say I need parts of controller code separated from it (so it doesn't get all messy), like some functions that are available throughout all $scope. What is the best way to accomplish this in AngularJS? Maybe it's not services as I thought …
The trick is to use a promise in your service to proxy the results.
The $http service returns a promise that you can resolve using then with a list or success and error to handle those conditions respectively.
This block of code shows handling the result of the call:
var deferred = $q.defer();
$http.get(productsEndpoint).success(function(result) {
deferred.resolve(result);
}).error(function(result) { deferred.reject(result); });
return deferred.promise;
The code uses the Angular $q service to create a promise. When the $http call is resolved then the promise is used to return information to your controller. The controller handles it like this:
app.controller("myController", ["$scope", "myService", function($scope, myService) {
$scope.data = { status: "Not Loaded." };
myService.getData().then(function(data) { $scope.data = data; });
}]);
(Another function can be passed to then if you want to explicitly handle the rejection).
That closes the loop: a service that uses a promise to return the data, and a controller that calls the service and chains the promise for the result. I have a full fiddle online here: http://jsfiddle.net/HhFwL/
You can change the end point, right now it just points to a generic OData end point to fetch some products data.
More on $http: http://docs.angularjs.org/api/ng.%24http
More on $q: http://docs.angularjs.org/api/ng.%24q
$http.get retuns a HttpPromise Object
Server.getAllData().then(function(results){
$scope.data = results;
})

How to wait till the response comes from the $http request, in angularjs?

I am using some data which is from a RESTful service in multiple pages.
So I am using angular factories for that. So, I required to get the data once from the server, and everytime I am getting the data with that defined service. Just like a global variables. Here is the sample:
var myApp = angular.module('myservices', []);
myApp.factory('myService', function($http) {
$http({method:"GET", url:"/my/url"}).success(function(result){
return result;
});
});
In my controller I am using this service as:
function myFunction($scope, myService) {
$scope.data = myService;
console.log("data.name"+$scope.data.name);
}
Its working fine for me as per my requirements.
But the problem here is, when I reloaded in my webpage the service will gets called again and requests for server. If in between some other function executes which is dependent on the "defined service", It's giving the error like "something" is undefined. So I want to wait in my script till the service is loaded. How can I do that? Is there anyway do that in angularjs?
You should use promises for async operations where you don't know when it will be completed. A promise "represents an operation that hasn't completed yet, but is expected in the future." (https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Promise)
An example implementation would be like:
myApp.factory('myService', function($http) {
var getData = function() {
// Angular $http() and then() both return promises themselves
return $http({method:"GET", url:"/my/url"}).then(function(result){
// What we return here is the data that will be accessible
// to us after the promise resolves
return result.data;
});
};
return { getData: getData };
});
function myFunction($scope, myService) {
var myDataPromise = myService.getData();
myDataPromise.then(function(result) {
// this is only run after getData() resolves
$scope.data = result;
console.log("data.name"+$scope.data.name);
});
}
Edit: Regarding Sujoys comment that
What do I need to do so that myFuction() call won't return till .then() function finishes execution.
function myFunction($scope, myService) {
var myDataPromise = myService.getData();
myDataPromise.then(function(result) {
$scope.data = result;
console.log("data.name"+$scope.data.name);
});
console.log("This will get printed before data.name inside then. And I don't want that.");
}
Well, let's suppose the call to getData() took 10 seconds to complete. If the function didn't return anything in that time, it would effectively become normal synchronous code and would hang the browser until it completed.
With the promise returning instantly though, the browser is free to continue on with other code in the meantime. Once the promise resolves/fails, the then() call is triggered. So it makes much more sense this way, even if it might make the flow of your code a bit more complex (complexity is a common problem of async/parallel programming in general after all!)
for people new to this you can also use a callback for example:
In your service:
.factory('DataHandler',function ($http){
var GetRandomArtists = function(data, callback){
$http.post(URL, data).success(function (response) {
callback(response);
});
}
})
In your controller:
DataHandler.GetRandomArtists(3, function(response){
$scope.data.random_artists = response;
});
I was having the same problem and none if these worked for me. Here is what did work though...
app.factory('myService', function($http) {
var data = function (value) {
return $http.get(value);
}
return { data: data }
});
and then the function that uses it is...
vm.search = function(value) {
var recieved_data = myService.data(value);
recieved_data.then(
function(fulfillment){
vm.tags = fulfillment.data;
}, function(){
console.log("Server did not send tag data.");
});
};
The service isn't that necessary but I think its a good practise for extensibility. Most of what you will need for one will for any other, especially when using APIs. Anyway I hope this was helpful.
FYI, this is using Angularfire so it may vary a bit for a different service or other use but should solve the same isse $http has. I had this same issue only solution that fit for me the best was to combine all services/factories into a single promise on the scope. On each route/view that needed these services/etc to be loaded I put any functions that require loaded data inside the controller function i.e. myfunct() and the main app.js on run after auth i put
myservice.$loaded().then(function() {$rootScope.myservice = myservice;});
and in the view I just did
ng-if="myservice" ng-init="somevar=myfunct()"
in the first/parent view element/wrapper so the controller can run everything inside
myfunct()
without worrying about async promises/order/queue issues. I hope that helps someone with the same issues I had.

Resources