Angular multiple ajax calls on same controller sometimes not working - javascript

I've just started using Angular with Ionic and so far it's great except for a problem with my ecommerce app where I am doing two ajax requests, one to load the vendor name and the other to load the products. I know I could put this in the same request but for other reasons it needs to be kept it separate.
Sometimes the vendor name gets set and sometimes it fails. It seems to be completely random and if I put a debugger in place it always works so appears to be something to do with timing.
.controller('VendorProductsCtrl', function($scope, $stateParams, $http) {
var vendor_name_url = 'URL FOR VENDOR NAME';
$http.get(vendor_name_url)
.success(function(response){
$scope.vendor_name = response.meta.vendor_name;
//debugger; this always works!
})
.error(function(data) {
console.log('server error');
})
$scope.products = [];
$scope.loadMore = function() {
var url = 'URL FOR PRODUCTS';
$http.get(url)
.success(function(response){
$scope.products = response.products;
})
.error(function(data) {
console.log('server error');
})
};
})
There seems to be something fundamental that I am missing on my new quest into Angular land. Thanks for your help.
P.S I'm using Ionic's infinite scroll which is calling loadMore()
Got it working, but seems a bit dodgy!
setTimeout(function () {
$scope.$apply(function () {
$scope.vendor_name = response.meta.vendor_name;
});
}, 500);
I'm not keen on waiting for 500ms or whatever it's set to if it's ready earlier, is there a better way?

Use $q.all
$q.all([
$http.get(vendor_name_url),
$http.get(url)
]).then(function(_responses){
$scope.vendor_name = _responses[0].meta.vendor_name;
$scope.products = _responses[1].products;
})

Seems to be an issue with the http request for the vendor name being finished before the $scope is applied when it works.
Move the definition of your $scope.vendor_name outside of the promise callback.
$scope.vendor_name = '';
$http.get(vendor_name_url).success(function(response){
$scope.vendor_name = response.meta.vendor_name;
})
.error(function(data) {
console.log('server error');
});
loadMore() works correctly because the $scope has the variable of products = []; during the wire-up of the controller. Everything after that hits the scope lifecycle of updating.
EDIT:
I would try to avoid doing the $scope.$apply function as you will then start using it everywhere and it makes the maintenance of your services and controllers very difficult.
Another way to approach this issue is to use resolves on your route.
http://odetocode.com/blogs/scott/archive/2014/05/20/using-resolve-in-angularjs-routes.aspx
This is a great approach to getting multiple data pieces into your controller defined by the route. The wire-up of the controller by the route is responsible for ensuring that data is available for dependency injection on the controller when it runs.

Related

Angular 1.5 Scope Variable not working from within PouchDB response function

So I've got the following code...
testApp.controller(...) {
$scope.results = [];
$scope.hasData = true;
$scope.results.push({
"name": "test"
}); // WORKS
db.get('table_people').then(function(response) {
console.log('success');
$scope.results.push({
"name": "test"
});
}); // this DOESN'T WORK even though the "success" message is printed...
});
And as you can tell from the comments, the first push to the array works, but the latter one doesn't. Top one can be printed out in the Angular template using {{ results }} but the latter one returns an empty array.
Edit: A solution has been found by using $timeout as the digest cycle wasn't running but sort of feels like a hacked together solution.
Edit: Solution...
db.get('table_people').then(function (response) {
console.log('success');
$timeout(function () {
$scope.results = response.data;
});
});
The solution code is slightly different as I no longer need the test data anymore due to the code functioning and can apply the response data directly.
You're missing a $digest cycle tick. Doing $scope.$digest() after you've pushed the data into $scope.results should fix the issue. Using $timeout is a bit of an overkill in this situation (and additional service to inject).

Async ran during digest phase

I'm running this code in an Angular service, immediately upon loading the page. The controller $scope is passed as an argument to the function this extract belong to. The function is a $q promise.
I am not able to figure out how can I let the controller know that scope.req.rows has been updated. If I add scope.$apply() right after it, I run into a running digest phase. If I use the $q resolve function, it returns and no more loop results are returned. scope.$evalAsync() and $timeout seem to have no effect (at least without setting a timeout > 0). Same goes for scope.$watch.
How can I let the controller know that values were updated?
for (var page = 0; page < nbPages; page++) {
(function (pageNum) {
that.get(url,
where,
res.pageSize * page,
res.pageSize)
.then(function Success(data) {
$log.info('Result of page ' + pageNum + ' received');
for (row in data) {
scope.req.rows++;
}
}).catch(function chunkFail(err) {
reject(err);
});
})(page);
I build simple demo and it`s works. Correct me if i wrong.
Updated:
i mocking http request and delay it form 1000ms to 30000ms. and i steel have't any scope problems.
http://jsbin.com/wasoxe/4/edit?js,output

$route.reload(); Issue

Hi im currenty using $route.reload to refresh the content of my controller Every time I update my Database. the problem is when updating huge list of data, Every Time I update my Database and run $route.reload my browser lose its ability to scroll up or down my browser, it works fine with smaller list of Data.
below is a sample of my code
$scope.Undone = function(id){
$scope.index = $scope.GetID ;
CRUD.put('/UndoJda/'+$scope.index).then(function(response){
toastr.info('Jda has been activated.', 'Information');
$route.reload();
});
}
Your best bet would be some sort of lazy loading/pagination. So in case it's a really large list, like in the tenths of thousands, it might even be a DOM rendering problem. Also, if that isn't the case, you should try using AngularJS's bind once(Available since 1.3), as well as track by which does not create a watcher for each object on the scope, in your template. Assuming you are using ngRepeat, let's say something like this:
...<ul>
<li ng-repeat="item in Items">
<b>{{item.name}}</b>
</li>
</ul>
Change that to something like the following, in case the data does not update often:
...<ul>
<li ng-repeat="item in Items track by $index">
<b>{{::item.name}}</b>
</li>
</ul>
As a side note, try to always have a dot in your model's name. $scope.Something.list, for eaxample. ("If you don't have a dot, you are doing it wrong" - Misko Hevery himself said this.).
When the data is huge, try to use $timeout and reload the page.
This would prevent very fast refreshes and will keep your page responsive.
$scope.Undone = function(id){
$scope.index = $scope.GetID ;
CRUD.put('/UndoJda/'+$scope.index).then(function(response){
toastr.info('Jda has been activated.', 'Information');
$timeout(function() {
$route.reload();
}, 200);
});
}
You can do it by using $interval
$interval(function() {
CRUD.put('/UndoJda/'+$scope.index).then(function(response){
toastr.info('Jda has been activated.', 'Information');
// Update scope variable
});
}, 2000);
and also don't use $route.reload();. because Angularjs supporting SPA (Single Page Application). if you using $route.reload();. Every time page will loading, So it's not good. you need just call the Service code in inside of interval.
First I would recommend removing usage of $route.reload(), your use case doesn't require the view re-instantiating the controller. Instead you should update the $scope variable that holds the collection of entities your presenting in the view. You will also want to consider adding UX features such as a loading indicator to inform the user about the long running task.
Something similar too the code below would achieve what your looking for. I am unaware of what your CRUD js object instance is, but as long as its Angular aware you will not need to use $timeout. Angular aware usually means non third party APIs, but you can use $q to assist in exposing third party ajax results to angular.
// angular module.controller()
function Controller($scope, EntityService) {
$scope.entityCollection = [];
$scope.loadingData = false; // used for loading indicator
// Something will initialize the entity collection
// this is however your already getting the entity collection
function initController() {
$scope.refreshCollection();
}
initController();
$scope.refreshCollection = function() {
$scope.loadingData = true;
EntityService.getEntitites().then(function(resp) {
$scope.entityCollection = resp;
$scope.loadingData = false;
});
}
$scope.Undone = function(id) {
$scope.index = $scope.GetID ;
CRUD.put('/UndoJda/' + $scope.index).then(function(response){
toastr.info('Jda has been activated.', 'Information');
$scope.refreshCollection();
});
}
}
// angular module.factory()
function EntityService($q, $http) {
return {
getEntitites: function() {
var deferred = $q.defer();
$http.post('/some/service/endpoint').then(function(resp) {
deferred.resolve(resp);
});
return deferred.promise;
}
}
}

Angular.js doesn't refresh the repeater after $scope.var changes but only after refresh

I thought 2 ways binding was angular thing:
I can post from a form to the controller, and if I refresh the page I see my input on the page:
$scope.loadInput = function () {
$scope.me.getList('api') //Restangular - this part works
.then(function(userinput) {
$scope.Input = $scope.Input.concat(userinput);
// scope.input is being referenced by ng-repeater in the page
// so after here a refresh should be triggered.
},function(response){
/*#alon TODO: better error handling than console.log..*/
console.log('Error with response:', response.status);
});
};
In the html page the ng-repeater iterates over the array of for i in input. but the new input from the form isn't shown unless I refresh the page.
I'll be glad for help with this - thanks!
Try $scope.$apply:
$scope.loadInput = function () {
$scope.me.getList('api') //Restangular - this part works
.then(function(userinput) {
$scope.$apply(function(){ //let angular know the changes
$scope.Input = $scope.Input.concat(userinput);
});
},function(response){
/*#alon TODO: better error handling than console.log..*/
console.log('Error with response:', response.status);
});
};
The reason why: Your ajax is async, it will execute in the next turn, but at this time, you already leaves angular cycle. Angular is not aware of the changes, we have to use $scope.$apply here to enter angular cycle. This case is a little bit different from using services from angular like $http, when you use $http service and handle your response inside .success, angular is aware of the changes.
DEMO that does not work. You would notice that the first click does not refresh the view.
setTimeout(function(){
$scope.checkboxes = $scope.checkboxes.concat([{"text": "text10", checked:true}]);
},1);
DEMO that works by using $scope.$apply
setTimeout(function(){
$scope.$apply(function(){
$scope.checkboxes = $scope.checkboxes.concat([{"text": "text10", checked:true}]);
});
},1);

AngularJS polling with $http and $timeout not updating view

In an AngularJS project, I'm able to display a list of events with a call to $scope.loadEvent, but then not able to update the view when polling the server using $http and $timeout. I can see that the correct $http calls and responses are being made/received.
A condensed version of the controller:
function EventsCtrl ($scope, $http, $timeout) {
$scope.myEvents = new Array();
var newMyEvents = new Array();
$scope.loadEvent = function(eventId) {
$http.get('http...').success(function(result) {
$scope.myEvents.push(result.data);
});
}
$scope.polling = function () {
var poller = function() {
newMyEvents = [];
for(var i=0; i< $scope.myEvents.length; i++) {
$http.get('http...').success(function(result) {
newMyEvents.push(result.data);
});
}
$timeout(poller, 2000);
}
$scope.myEvents = newMyEvents;
poller();
}
}
and the view:
<div ng-controller="EventsCtrl" ng-init="polling()">
I've tried $scope.$apply(), which returns Error: $apply already in progress as it seems $http is already "inside" Angular.
Any and all thoughts appreciated. Thanks.
In some cases you will need to use $scope.$apply();
Try performing:
$scope.$apply(function() {
$scope.myEvents = newMyEvents;
}
EDIT
I misunderstood what's happening completely. The problem is that you are firing all of those queries asynchronously, so you have no way of knowing when each of them finishes. Also, $timeout automatically executes $scope.$apply for you. If you want to be in control of when to actually $apply you can use setTimeout() instead.
Perhaps you could perform $scope.myEvents = newMyEvents within the callback of the last $http request (you don't have 100% guarantee it would actually get the data last). Best way would be executing one call to the backend that fetches all of the resource for you so that you can update the variable in the .success function directly. (if that is achievable)
More about $apply and how to prevent the error

Categories

Resources