How to make html wait for a function in AngularJS controller - javascript

I have a object in mainController.js that is set as default as 99.
I am obtaining user location and do running some other function with it to calculate this value.
However, When I load the page, the page seems to load faster than this process. Therefore it displays 99 instead of the calculated value.
If I put console.log after the calculation, the object is successfully changed.
edit1:
status.success( function(data)
{
$scope.current = data;
$scope.$broadcast('back_end_connected');
});
$scope.getLocation = function()
{
if (navigator.geolocation)
{
navigator.geolocation.getCurrentPosition(function (position){
$scope.location = {lat: position.coords.latitude, lng: position.coords.longitude};
$scope.$broadcast('location_obtained');
$scope.buildDist();
$scope.fetch();
//$scope.getRec();
});
}
else{
alert("Geolocation is not supported by this browser.");
}
};
var dirBuilt = false;
$scope.$on('location_obtained', function(){
$scope.buildDist = function()
{
if(dirBuilt === false)
{
$scope.facilities[0].distance = distCalc($scope.location.lat,$scope.location.lng,$scope.facilities[0].location.lat,$scope.facilities[0].location.lng);
$scope.facilities[1].distance = distCalc($scope.location.lat,$scope.location.lng,$scope.facilities[1].location.lat,$scope.facilities[1].location.lng);
$scope.facilities[2].distance = distCalc($scope.location.lat,$scope.location.lng,$scope.facilities[2].location.lat,$scope.facilities[2].location.lng);
$scope.facilities[3].distance = distCalc($scope.location.lat,$scope.location.lng,$scope.facilities[3].location.lat,$scope.facilities[3].location.lng);
$scope.facilities[4].distance = distCalc($scope.location.lat,$scope.location.lng,$scope.facilities[4].location.lat,$scope.facilities[4].location.lng);
$scope.facilities[5].distance = distCalc($scope.location.lat,$scope.location.lng,$scope.facilities[5].location.lat,$scope.facilities[5].location.lng);
$scope.$broadcast('dist_obtained');
dirBuilt = true;
alert("aaa: "+ $scope.facilities[0].distance);
}
};
});
that "alert("aaa: "+ $scope.facilities[0].distance);" returns the value I want it to display but it is not displayed on the page....
(ng-bind would not work for some reason...)
How can I make the html wait for the operation? Thanks

You should not make HTML wait until you finish your Js code ! What you should be doing is showing a placeholder value - Loading image so that user know the page is loading some data.
Once you are done with your calculation, hide /replace the loading image with the data you want to show.
Quick example
Your view markup will have some HTML element to show the progress bar.And all your other contents will be in another div
<body ng-app="yourApp" ng-controller="yourCtrl as vm">
<div ng-show="loaderCount>0"> Loading something..</div>
<div ng-show="loaderCount==0">
<h4>{{userScore}}</h4>
</div>
</body>
And in your angular controller, You have a scope variable called loaderCount which you will increase everytime when you are doing some operation (http call/Long running function execution etc..). When you get your result, You decrease this variable value back. In your View You are hiding and showing the Loading Pane based on this value.
var yourApp= angular.module('yourApp', []);
var ctrl = function($scope, $http) {
var vm = this;
vm.loaderCount = 0;
vm.someValue = "";
vm.actionItems = [{ Name: "Test" }, { Name: "Test2" }];
vm.loaderCount++;
$http.get("../Home/GetSlowData").then(function(s) {
vm.loaderCount--;
vm.someValue = s.data;
});
};
yourApp.controller('yourCtrl', ['$scope', '$http', ctrl]);
This might not be the best angular code. But this will give you an idea about how to handle this use case. You should be using services to talk to Http endpoints instead of directly using $http in your angular controller.
Note that, $http.get returns a promise which allows you do things when you get the response from this asynchronous operation (the then event). You should make sure that your time taking calculation is returning a promise.

You can bind with the ngBind directive instead of {{}} and not write to the binded property when the calculation is done.
This will hide the result from the view while it is null.

Try to do something like this:
$scope.value = "Loading";
or
$scope.value = "";
$scope.calculate = function() {
$scope.value = yourcalculation;
}
$scope.calculate();
Or in your case i think if you use $scope.$apply() after you add data to your scope element will do the trick

Try this :
$scope.$on('location_obtained', function(){
$scope.buildDist = function()
{
if(dirBuilt === false)
{
$scope.facilities[0].distance = distCalc($scope.location.lat,$scope.location.lng,$scope.facilities[0].location.lat,$scope.facilities[0].location.lng);
$scope.facilities[1].distance = distCalc($scope.location.lat,$scope.location.lng,$scope.facilities[1].location.lat,$scope.facilities[1].location.lng);
$scope.facilities[2].distance = distCalc($scope.location.lat,$scope.location.lng,$scope.facilities[2].location.lat,$scope.facilities[2].location.lng);
$scope.facilities[3].distance = distCalc($scope.location.lat,$scope.location.lng,$scope.facilities[3].location.lat,$scope.facilities[3].location.lng);
$scope.facilities[4].distance = distCalc($scope.location.lat,$scope.location.lng,$scope.facilities[4].location.lat,$scope.facilities[4].location.lng);
$scope.facilities[5].distance = distCalc($scope.location.lat,$scope.location.lng,$scope.facilities[5].location.lat,$scope.facilities[5].location.lng);
$scope.$broadcast('dist_obtained');
dirBuilt = true;
alert("aaa: "+ $scope.facilities[0].distance);
$scope.$apply(); // This should do the trick
}
};
});

Are you using a promise on the service? The function running your calculation can be in a separate angular factory which has .success and .error events to tap into. If the calculation is a success. You can pass the data back to your controller and then bind that data to the controller scope. I'll be at a computer soon and will add some code to explain further if needed.
This would be your distance calculator, I used a Factory over a service but you can read about why to use one over the other
A couple of things to keep in mind. You geolcation is more of a service then something to control since you're requesting from the user, you could add it to the below factory to expand the factories capabilities and allow you to use getLocation in other controllers.
make sure you add the distance service to your html document
also make sure you put the distance service in your controller and on the main angular app.
distanceService.js
(function(){
'use strict'
var distSrvc= angular.module("distanceService",[])
distSrvc.factory('DistanceCalc',['$http',function($http){
return{
getDistance:function(facilitiesData,locationData){
Object.keys(facilitiesData).length; // or if it's already an array of facilities use facilitiesData.length
// if its a javascript object and distance is already defined
for (var distance in facilitiesData) {
if (facilitiesData.hasOwnProperty(distance)) {
facilitiesData.distance = distCalc(locationData.lat,locationData.lng,facilitieData.location.lat,facilitieData.location.lng);
}
}
// if its an array of objects
for (var i = 0 ; i< facilitiesData.length;i++){
facilitiesData[i].distance = distCalc(locationData.lat,locationData.lng,facilitieData.location.lat,facilitieData.location.lng);
}
return facilitiesData
}
})();
Then in your controller you'll need to load the service for use.
yourcontroller.js this will give errors if you don't load it on the html page and add it to the main angular app.
(function(){
'use strict'
var myController= angular.module("distanceService",[])
myController.controller('myController',['$scope','DistanceCalc',function($http,DistanceCalc){ // see how i added the service and passed it to the function
$scope.getLocation = function(){
if (navigator.geolocation)
{
navigator.geolocation.getCurrentPosition(function (position){
$scope.location = {lat: position.coords.latitude, lng: position.coords.longitude};
$scope.$broadcast('location_obtained');
$scope.buildDist();
$scope.fetch();
//$scope.getRec();
});
}
else{
alert("Geolocation is not supported by this browser.");
}
};
// this line below sends the data to the service
DistanceCalc.getDistance($scope.facilities,$scope.location)
.success(function(data){
//success i'm done running distance calculations and now i want to update the facilties object with the added distance
$scope.facilities = data
})
.error(function(data){
// some error occurred and data can tell you about that error
})
}
})();

Related

Do async calls in services block component load in angularjs?

The angularjs guide contains an example detailing an async call in a service.
The following code is given
angular.module('finance3', [])
.factory('currencyConverter', ['$http', function($http) {
var currencies = ['USD', 'EUR', 'CNY'];
var usdToForeignRates = {};
var convert = function(amount, inCurr, outCurr) {
return amount * usdToForeignRates[outCurr] / usdToForeignRates[inCurr];
};
var refresh = function() {
var url = 'https://api.exchangeratesapi.io/latest?base=USD&symbols=' + currencies.join(",");
return $http.get(url).then(function(response) {
usdToForeignRates = response.data.rates;
usdToForeignRates['USD'] = 1;
});
};
refresh();
return {
currencies: currencies,
convert: convert
};
}]);
Since the refresh function is async, how does angularjs ensure that the data is loaded before loading the controller which accesses the data returned by the refresh function, ie the data contained in the usdToForeignRates object.
I assume there is some blocking going on somewhere, otherwise when the controller accesses the object returned, it will get undefined values.
I want to understand the flow, does angular internally ensure that service loads before injecting it into the controller?

Best way to pass variables between controllers

I am using three Angular controllers:
**Controller1**
var fetchStudentDetails = function(){
var sDetails = myService.getList(//url-1 here);
sDetails.then(function (data) {
$scope.studentData = data.list;
var studentId = $scope.studentData[0].id;
});
}
fetchStudentDetails();
$scope.loadSecondLevel = function(){
$state.go('secondLevel');
}
**Controller2**
var fetchClassDetails = function(){
var sDetails = myService.getList(//url-2 here);
sDetails.then(function (data) {
$scope.classData = data.list;
var className = $scope.classData[0].name;
});
}
fetchClassDetails();
$scope.loadThirdLevel = function(){
$state.go('thirdLevel');
}
**Controller3**
$scope.putStudentDetails = function(){
// Here I need studentId,className for updateResource
var sDetails = myService.updateResource(//url-3 here);
sDetails.then(function (data) {
});
}
Where I have to pass studentId (in Controller1), className (in Controller2) into a function which in Controller3. I tried with $rootScope, it is working but when refresh the page $rootScope values become empty. Does anyone know how to do this?
Your question could be split into two aspects:
1. How to share data between controllers
The best practice to share data in Angular 1.x is using factory, store the shared data in a factory service, and expose access methods to controllers:
factory('DetailData', function(myService, $q){
var _details;
function __getDetailData(){
return details
}
function __setDetailData(){
return myService.getList().then(function(data){
_details = data;
})
}
return {
getDetailData: __getDetailData,
setDetailData: __setDetailData
}
})
controller('myContrller', function(DetailData, $scope){
$scope.data = DetailData.getDetailData();
})
2. How to persist data when page refreshed,
you can use localStorage to keep data persistent during page reloading, many tools & libraries can achieve this, for example ngStorage, or you could reset the data from server every time your angular application started:
//this would register work which would be performed
//when app finish loading and ready to start.
angular.module('app').run(function(DetailData){
DetailData.setDetailData();
})
Depending on what problem you are solving.
There are three options:
Is to save data to $rootScope
Is to use $scope.$emit & $scope.$on functions.
Use a custom Service to store the data
And if you need to save data, so it was available after full page reload - localStorage.
Hey this question are responded in Passing data between controllers in Angular JS?
But the simple response is in the services.

Setting controller attributes inside ajax call

I am new in AngularJS and I'm with a doubt about the controller attributes. I created an attribute called anuncio, this attribute has an array of objects as showed in the image bellow:
var anuncioModule = angular.module('anuncioModule',[]);
anuncioModule.controller('RegistrationController',['$http',function ($http){
this.anuncios;
this.loadAnuncios = function loadAnuncios(){
$http.get('http://localhost:8080/pederofer/anuncios/get.action').then(function(result){
this.anuncios.push.apply(this.anuncios, result.data);
});
}
}]);
When I call my webservice with the function loadAnuncios and try to set the values directly using "this.anuncios" i get the message "this.anuncios is undefined". but if i create a var called anuncs and set "this.anuncios = anucs", and instead of set my AJAX call directly into this.anuncios I set to anucs as the image bellow, It works.
var anuncioModule = angular.module('anuncioModule',[]);
var anuncs =[];
anuncioModule.controller('RegistrationController',['$http',function ($http){
this.anuncios = anuncs ;
this.loadAnuncios = function loadAnuncios(){
$http.get('http://localhost:8080/pederofer/anuncios/get.action').then(function(result){
anuncs.push.apply(anuncs, result.data);
});
}
}
My question is, why it works?
I might suggest going about what you're doing in a different way.
var anuncioModule = angular.module('anuncioModule',[]);
anuncioModule.controller('RegistrationController', ['$scope', '$http', function ($scope, $http) {
$scope.anuncios = [];
$scope.loadAnuncios = function() {
$http.get('http://localhost:8080/pederofer/anuncios/get.action').then(function(result) {
$scope.anuncios = result.data;
});
};
}
I don't know if that's exactly what you're after but maybe that will make a lightbulb go off for you.

AngularJS - Improving service call and data binding performance

I have an Angular service that goes away to retrieve a pretty big JSON file (nearly 10,000 lines).
The problem i am facing, is that it is taking some time to bind the data to the front-end (as expected).
Sample controller:
$scope.dataLoaded = false;
serviceReport.getData( function (data) {
$scope.data1 = data.data1;
$scope.data2 = data.data2;
$scope.data3 = data.data3;
$scope.data4 = data.data4;
$scope.data5 = data.data5;
$scope.data6 = data.data6;
$scope.data7 = data.data7;
$scope.data8 = data.data8;
$scope.data9 = data.data9;
$scope.data10 = data.data10;
$scope.data11 = data.data11;
$scope.data12 = data.data12;
$scope.data13 = data.data13;
$scope.data14 = data.data14;
$scope.data15 = data.data15;
$scope.data16 = data.data16;
$scope.data17 = data.data17;
$scope.dataLoaded = true;
});
Service:
app.factory('serviceReport', function($http) {
return {
getData: function(value,done) {
$http.get('data.json', {
})
.success(function(data) {
done(data);
})
.error(function(error) {
alert('An error occured');
});
}
}
});
I have ng-cloak on my HTML element, when dataLoaded = true, this is removed as it indicates the data is available to be displayed.
How can i improve the service call/data bind? Would splitting the call help?
Server-side solution would be to reduce the size of the response and make more requests with smaller responses. Do you actually need the whole response at start? You have to be aware that binding the whole response will generate many watchers, which will slow down all subsequent digests.
Client-side solution would be to bind the response part by part in a loop as a callback parameter for $scope.$apply() or even $timeout().

AngularJS promise is resolved before data is loaded

In my app, I have to fetch some JSON data and assign it to an array before the page is loaded. This is my code for fetching the JSON using the CardService service:
cards = [];
var cs = {
...
fetchCards: function() {
var d = $q.defer();
$http.get("data/cards.php").success(function(data) {
cards = data;
d.resolve();
}).error(function(data, status) {
d.reject(status);
});
return d.promise;
},
getCards: function() { return cards; };
...
}
In the controller's resolve block, I have the following:
WalletController.resolve = {
getCards: function(CardService) {
CardService.fetchCards().then(loadView, showError);
}
}
And in the actual controller, I have the following:
function WalletController($scope, CardService) {
$scope.cards = CardService.getCards();
}
The problem is, the fetchCards function in the service seems to resolve the promise before the JSON data is assigned to the cards variable. This leads to my view loading with blank data until I refresh a couple times and get lucky.
I can confirm the late loading as when I log the cards variable in the console, I get an empty array at line 122 (when my view is loaded) and a full array at line 57 (when the JSON call is successful). Line 57's code somehow executes after the view is loaded.
How do I fix this?
I haven't used resolve but I'm throwing this out there just in case the issue you are having is related to binding to an array returned from a service.
If you are returning your cards array from a service and binding to it in the UI you may want to try to populate that same array instead of setting cards = data; (which will overwrite the local cards with a new array which is not bound to the UI).
Something like:
fetchCards: function() {
var d = $q.defer();
$http.get("data/cards.php").success(function(data) {
cards.length = 0;
for(var i = 0; i < data.length; i++){
cards.push(data[i]);
}
d.resolve();
}).error(function(data, status) {
d.reject(status);
});
return d.promise;
},
See this fiddle for a working example of what I'm trying to describe. Clicking the first button multiple times will update the view but once you click on the second button the binding will be broken.
The main difference between the two is:
First button uses data.length = 0 and data.push() to retain the original array's reference
Second button overwrites the original data array reference with a new one using data = newArray
Update: Also, as Mark Rajcok, mentioned below you can use angular.copy to retain the original array's reference by emptying it out and adding new ones from the source like this:
fetchCards: function() {
var d = $q.defer();
$http.get("data/cards.php").success(function(data) {
angular.copy(data, cards);
d.resolve();
}).error(function(data, status) {
d.reject(status);
});
return d.promise;
},

Categories

Resources