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
Related
I'd like to check an image to see if the resource is available, before displaying it. I found a good way to do that in AngularJS here: Angular js - isImage( ) - check if it's image by url
But every time I try to implement it, an infinite loop is triggered, even if I reduce the function to its simplest form in a codepen : https://codepen.io/anon/pen/mBwgbE
test image function (js)
$scope.testImage = function(src) {
console.log('function triggered');
Utils.isImage(src).then(function(result) {
return "result";
});
};
Usage (html)
<h3>Image link broken<h3>
<p>{{testImage('anylink')}}</p>
<h3>Image link OK<h3>
<p>{{testImage('http://lorempixel.com/400/200/sports/1/')}}</p>
Can anyone explain this behaviour to me, and help me fix it?
Angular runs the digest loop, and interprets your template. It sees {{testImage('anylink')}} and calls into it. This calls into Utils.isImage, which creates a promise. The promise is returned to testImage, but testImage itself doens't return anything, so the template shows nothing.
A little later, the promise resolves. Angular sees this, so it runs the digest loop and interprets your template. It sees {{testImage('anylink')}} and calls into it. This calls into Utils.isImages, which creates a prom... oh crap, we're in an loop. It's going to call isImage, which creates a promise, and then when that promise resolves, it interprets the template again and calls isImage, starting all over.
Instead, i would recommend that when your controller loads, you create the promises right then, and when they resolve, you stick whatever values you need from them onto the controller as concrete values. Something like this:
function myController($scope, Utils) {
$scope.anyLink = null;
$scope.sportsLink = null;
Utils.isImage('anyLink')
.then(function (result) { $scope.anyLink = result });
Utils.isImage('http://lorempixel.com/400/200/sports/1/')
.then(function (result) { $scope.sportsLink = result });
$scope.heading = "My Controller";
}
And then on your template, interact with $scope.anyLink or $scope.sportsLink
$scope.testImage is automatically watched by angular to see change of testImage.
As a result you can stop infinite loop by using $scope.cache variable.
$scope.testImage = function(src) {
console.log('function triggered');
if($scope.cache[src] == "result")
return "result";
Utils.isImage(src).then(function(result) {
$scope.cache[src] = "result";
return "result";
});
};
It was tested on your codepen.
I'm having the most difficult time trying to find a way to make sure the parent scope's collection is updated with the saved information from a modal.
The parent has a collection of event speakers, and the modal adds one speaker to the event. I want for the new speaker to be added to the page once the modal OK button is clicked.
The data is saved and the modal closes, but the model isn't updated unless I refresh the page.
Here's my controller method that updates the collection of speakers. $scope.speakers gets bound to a repeating object in the page.
$scope.updateSpeakersList = function () {
factory.callGetService("GetSpeakers?eventId=" + $scope.event.eventId)
.then(function (response) {
var fullResult = angular.fromJson(response);
var serviceResponse = JSON.parse(fullResult.data);
$scope.speakers = serviceResponse.Content;
LogErrors(serviceResponse.Errors);
},
function (data) {
console.log("Unknown error occurred calling GetSpeakers");
console.log(data);
});
}
Here's the promise where the modal should be calling the previous method, and therefore updating the page.
$scope.openModal = function (size) {
var modalInstance = $modal.open({
templateUrl: "AddSpeakerModal.html",
controller: "AddSpeakerModalController",
size: size,
backdrop: "static",
scope: $scope,
resolve: {
userId: function() {
return $scope.currentUserId;
},
currentSpeaker: function () {
return ($scope.currentSpeaker) ? $scope.currentSpeaker : null;
},
currentSessions: function () {
return ($scope.currentSpeakerSessions) ? $scope.currentSpeakerSessions : null;
},
event: function () {
return $scope.event;
},
registration: function() {
return $scope.currentUserRegistration;
}
}
});
modalInstance.result.then(function (savedSpeaker) {
$scope.savedSpeaker = savedSpeaker;
$scope.updateSpeakersList();
}, function () {
console.log("Modal dismissed at: " + new Date());
});
};
Why is the model not updating?
It's hard to know for sure without having a minimal example that reproduces the problem, but your problem might be that you are updating a 'shadow scope'. This is a pecularity in Javascript which causes the object to be copied instead of modified.
Try adding $scope.$apply() after doing the change in updateSpeakerList().
Check this post and then play around with it.
AngularJS - Refresh after POST
What worked for me was pushing the data to my scope on success then setting the location, so after posting new data mine looked like:
$scope.look.push(data);
$location.path('/main');
The issue here isn't that my data wasn't updating necessarily... the real issue is that the moments that I was attempting to update it were the wrong moments for how my app is put together. I was attempting to update the underlying model BEFORE the updates were available, without realizing it. The AJAX calls were still in progress.
Within my modal, when OK is clicked, it runs through a few different GETs and POSTs. I was calling the update outside of these calls, assuming that it would be called sequentially once the AJAX calls were done. (This was wrong.)
Once I moved the $scope.updateSpeakersList() call to be within the final AJAX success call to my factory, everything appears to be working as desired.
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
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;
}
}
}
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.