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);
Related
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.
In this code my console.log logs undefined. I am trying to access $scope property from other $scope method. How to do this properly?
AppControllers.controller('DepartureLocationCtrl', [
'$scope','$http',
function($scope,$http){
$http.get('/airports').success(function(data){
$scope.departureLocations = data;
});
$scope.showSuggestions = function(){
console.log($scope.departureLocations);
}
$scope.showSuggestions()
}]);
Your code tries to log the value of $scope.departureLocations immediately after you have sent the http request to get them from the backend. At this time, the http response has not come back yet, and the $scope.departureLocations = data; line has not been executed yet.
The first A in AJAX means Asynchronous.
Move $scope.showSuggestions() inside the function passed to success(). And BTW, use then() instead of success(): success() is deprecated.
AppControllers.controller('DepartureLocationCtrl', [
'$scope','$http',
function($scope,$http){
$http.get('/airports').then(function(response){
$scope.departureLocations = response.data;
$scope.showSuggestions()
});
$scope.showSuggestions = function(){
console.log($scope.departureLocations);
}
}]);
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().
$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/
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.