Error: $digest already in progress in angularjs when using alert - angularjs

I am just getting the json data from the services in the controller.
And I am using a callback function to print the success message when it got loaded. It is working fine but it is also throwing an error which I mentioned in the question
//JSON file
{
"pc":"name"
}
// angular services
var service = angular.module('Services', ['ngResource']).
factory('Widgets', function($resource){
return $resource('/json/home.json', {}, {
query: {method:'GET', params:{}, isArray:false}
});
});
//controller
function editWidget($scope, Widgets) {
$scope.data = Widgets.query(function(data) {
alert("Success Data Loaded ---> " + JSON.stringify(data.pc));
});
}

alert, as well as confirm and prompt will pause the execution of code (blocks the thread), during which timeouts and intervals go all haywire if they should have been triggered during the pause. The $digest loop is made up of two smaller loops, which process $evalAsync queue and the $watch list. The $evalAsync queue is used to schedule work which needs to occur outside of current stack frame, but before the browser's view render. This is usually done with setTimeout(0). Your alert during this time causes the problem.

You can use $timeout to execute an alert after the digest cycle is done and this way avoids this error.
$timeout(function () {
alert('Alert text');
});
Also don't forget to inject $timeout into your directive

if(!confirm('Your message')){
return false;
}else {
return false;
}
Return false in both cases.

#TheSharpieOne is right, It's work for me.
function delayalert(messagestr){
setTimeout(function(){
alert(messagestr);
},0);
}

Related

Wait for event to happen in server side in angular JS

I am really new to angularJS. I need to develop a page where angular JS wait for a event to happen at server side so angular JS should keep checking server using $http call in every 2 seconds. Once that event completes Angular should not invoke any $http call to server again.
I tried different method but it gives me error like "Watchers fired in the last 5 iterations: []"
Please let me know how to do it.
Following is my code
HTML
<div ng-controller="myController">
<div id="divOnTop" ng-show="!isEventDone()">
<div class="render"></div>
</div>
</div>
Angular JS
var ngApp = angular.module("ngApp",[]);
ngApp.controller('myController', function ($scope, $http) {
$scope.ready = false;
$scope.isEventDone = function () {
$scope.ready = $scope.getData();
return $scope.ready;
};
$scope.getData = function () {
if (! $scope.ready) {
$http.get("/EventManager/IsEventDone")
.then(function (response) {
$scope.ready = Boolean(response.data);
});
}
};
setInterval($scope.isPageReady, 5000);
});
A few things here.
I'm not convinced the accepted answer actually works nor solves the initial problem. So, I'll share my 2 cents here.
$scope.ready = $scope.getData(); will set $scope.ready to undefined each time since this method doesn't return anything. Thus, ng-show="!isEventDone()" will always show the DOM.
You should use angular's $interval instead of setInterval for short-polling in angular.
Also, I've refactored some redundancy.
var ngApp = angular.module("ngApp",[]);
ngApp.controller('myController', function ($scope, $http, $interval) {
var intervalPromise = $interval($scope.getData, 5000);
$scope.getData = function () {
if (! $scope.isEventDone) {
$http
.get("/EventManager/IsEventDone")
.then(function (response) {
$scope.isEventDone = Boolean(response.data);
if($scope.isEventDone) {
$interval.cancel(intervalPromise);
}
});
}
else {
$interval.cancel(intervalPromise);
}
};
});
This should work and solve your initial problem. However, there's a scenario where your server may be on a high load and takes 3 seconds to respond. In this case, you're calling the server every 2 seconds because you're waiting for 5 seconds after the previous request has started and not waiting for after the previous request has ended.
A better solution than this is to use a module like async which easily handles asynchronous methods. Combining with $timeout:
var ngApp = angular.module("ngApp",[]);
ngApp.controller('myController', function ($scope, $http, $timeout) {
var getData = function(cb){
if(!$scope.isEventDone) return cb();
$http.get("/EventManager/IsEventDone")
.then(function (response) {
$scope.isEventDone = Boolean(response.data);
cb();
});
};
// do during will run getData at least once
async.doDuring(getData, function test(err, cb) {
// asynchronous test method to see if loop should still occur
// call callback 5 seconds after getData has responded
// instead of counting 5 seconds after getData initiated the request
$timeout(function(){
cb(null, !$scope.isEventDone);
// if second param is true, call `getData()` again otherwise, end the loop
}, 5000);
}, function(err) {
console.log(err);
// if you're here, either error has occurred or
// the loop has ended with `$scope.isEventDone = true`
});
});
This will call the timeout after the request has ended.
A better alternative, if you have control of the server, is to use a websocket which will enable long-polling (server notifies the client instead of client making frequent requests) and this will not increase significant load on the server as clients grow.
I hope this helps
In your example $scope.pageIsReady does not exist. What you could do is inject the $timeout service into your controller and wrap your http call inside of it:
var timeoutInstance = $timeout(function(){
$http.get("/EventManager/IsEventDone")
.then(function (response) {
$scope.ready = Boolean(response.data);
if($scope.ready){
$timeout.cancel(timeoutInstance);
else
$scope.getData();
}
});
},5000);
cancel will stop the timeout from being called. I have not tested this but it should be along those lines.
Also not sure what type of backend you are using but if it is .net you could look into SignalR which uses sockets so the server side tells the front end when it is ready and therefore you no longer need to use polling.

ng-bind not updating, only after refreshing the page

Using $resource to get the data, the pages have the data from the previous page until I refresh it. I've read that it's because angular itself doesn't know that it's been updated. However even after using $watch and $applyAsync, it doesn't work. ($apply gives me a digest error).
angular.module('app.factory',[])
.factory('teamService', ['$resource','$routeParams',function($resource, $routeParams){
return $resource('/api/team/:id', {id: $routeParams.id},{
update: {
method: 'PUT'
}
});
}]);
(function(){
angular.module('app.team',[])
.controller('TeamController',['teamService','$scope','$rootScope','$resource',function(teamService,$scope,$rootScope, $resource){
$scope.teamid = teamService.query();
Your code looks good to me, so I think this is a routing issue. Maybe you could try resolving the resource in the router and not in the controller. https://github.com/angular-ui/ui-router/wiki#resolve
I would also try assigning when the promise resolves:
teamService.query().$promise.then(function (result) {
$scope.teamid = result;
}
And to force a digest you could try $timeout instead of $apply. ($timeout needs to be injected into the controller.) $apply gives an error if called during a digest, $timeout waits until the current digest has completed (it still uses $apply internally).
teamService.query().$promise.then(function (result) {
$timeout(function(){
$scope.teamid = result;
});
});
Sorry, not a real answer - the comment box is so limited at times.

Angular: $apply already in progress in IE11, but not in FF and Chrome

I got an
<input type="file" id="aircraftList" name="aircraftList" file-upload multiple/>
bound to a directive
angular.module("app.directives").directive('fileUpload', function () {
return {
scope: true,
link: function (scope, el, attrs) {
el.bind('change', function (event) {
scope.$emit("fileSelected", { files: event.target.files, field: event.target.name });
});
}
};
});
I catch this event in a controller:
$scope.$on("fileSelected", function (event, args) {
$scope.$apply(function () {
switch (args.field) {
case "aircraftList":
self.attachments.aircraftList = args.files;
break;
default:
break;
}
});
});
For some reason this works perfectly well in Chrome and Firefox, but fails in IE11 with the following error:
If I dont put the $apply, chrome is not updating the view, but IE is. If I put the $apply, Chrome works perfect and IE breaks.
Anyone knows what and why it goes wrong here?
Actually, chrome and FF javascript engines are very fast when compared to IE11 javascript engine.
Hence, when the $scope.$on("fileSelected" is triggered in chrome &
FF, the previous $digest loop will be completed at the time of
$scope.$apply is executed and hence no errors. As there is no
$digest cycle is running at this stage, we need another $digest
cycle to update the view with help of $scope.$apply and without
this, view won't be updated.
As IE is comparatively slow on the same scenario above,
$scope.$apply is throwing error as there is one $digest loop is
running currently. Hence, without $scope.$apply, the view will get
updated with help of the running $digest cycle.
When we use $timeout as said by other users, it will start executed once the current $digest cycle is completed and making sure to update the view with another $digest loop.
Hope it will clarify you :-)
$scope.$on("fileSelected", function (event, args) {
$timeout(function () {
switch (args.field) {
case "aircraftList":
self.attachments.aircraftList = args.files;
break;
default:
break;
}
});
});
Firstly, you're calling $apply from within an already executing $digest cycle. Chrome/FF may be fine for you, but that's really down to luck on your part. Really on this you're at the mercy of the user's PC performance. Angular will always trigger off it's own $digest cycle whenever it is transmitting events. Your $scope.$emit will be triggering $digest here.
You've got a few problems here though which are going to be tying everything up in knots, and will cause further problems of this kind. Normally there should be no need for you to trigger a $digest cycle unless you are responding to events triggered from outside Angular.
Your directive file-uploader seems far too dependent on your view model - it's even telling the controller which field it should be storing the returned data in. Rememember, that's the controllers job! I've changed your code around a little to ensure that there's no need to have two simultaneous $apply cycles, which eliminates your problem, and also to tidy up the code a little.
I've changed the directive to use two-way data-binding instead of emitting events via the rootscope - big improvement to performance, and encapsulation of functionality.
app.directive('testUploader', [function() {
return {
scope: {
ngModel: '='
},
link: function(scope, el) {
el.bind('change', function(event) {
if (event.target.files && event.target.files.length > 0) {
angular.forEach(event.target.files, function (newFile) {
scope.ngModel.push(newFile);
});
scope.$apply();
}
});
}
};
}]);
This vastly simplifies the controller which now has no need to handle client events - it simply mediates between the view model and any underlying data model up on your server
app.controller("testctrl", ['$scope', function($scope) {
$scope.data = {
aircraftList: []
};
}]);
See working JSFiddle here: https://jsfiddle.net/z2yLad92/27/
Hope this helps.
i don't know why both the browsers are behaving diffrently. But I know to get rid of this error and make it work
You have used $apply(). If you get $rootScope:inprog error, it means already a digest cycle is running. To avoid this error wrap your $apply function under a timeout condition.
like this,
$scope.$on("fileSelected", function (event, args) {
$timeout(function () {
$scope.$apply(function () {
switch (args.field) {
case "aircraftList":
self.attachments.aircraftList = args.files;
break;
default:
break;
}
});
},500);
});
I hope this works for you.
You stuck into this issue as your code try to trigger digest cycle before one got completed and that you are facing only in IE probably because of slow nature of IE. so my idea is to use $scope.$evalAsync
$scope.$evalAsync(function () {
switch (args.field) {
case "aircraftList":
self.attachments.aircraftList = args.files;
break;
default:
break;
}
});
$scope.$evalAsync will adds the supplied function to a queue that will be drained at the beginning of the next loop in a digest cycle.
I hope this work for you.
Thanks
You can apply scope by checking first
(!$scope.$$phase) ? $scope.$apply() : null;
It will not apply scope if it is already in progress.
I don't think any of the others answers here (at time of posting) are correct.
No doubt there is some sort of timing issue or similar between Chrome/FF vs IE11, but that isn't the fundamental problem here.
The fundamental problem is this:
el.bind('change', function (event) {
scope.$emit("fileSelected", { files: event.target.files, field: event.target.name });
});
You shouldn't be emitting an AngularJS event, when you're not in an AngularJS digest cycle.
The code should be this:
el.bind('change', function (event) {
scope.$apply(function() {
scope.$emit("fileSelected", { files: event.target.files, field: event.target.name });
});
});
You can then remove the $scope.$apply() call from your second piece of code.

Refresh scope on every x time using $timeout

I am a newbie to angular. I want to use $timeout of angular to refresh scope after few minutes. I am working on an social app where i need to refresh notification scope after few minutes. Getting notification from a http request using service.
JS:
App.factory('MyService' ,function($scope,$timeout){
return{
notification:return function(callback){
$timeout(function(){
$http.get("notification/get").success(callback)
},100000);
}
});
function Controller($scope,MyService){
MyService.notification(function(result){
$scope.notification =data;
});
}
Now how can i make http request after few minutes let'say 1 minute and refresh notification scope. I tried using $timeout but things are not working fine.
But i would suggest to move the $interval to the controller.
App.factory('MyService' ,function($scope,$timeout){
return{
notification: function(){
return $http.get("notification/get").success(function(response){
return response.data;
});
}
});
function Controller($scope,MyService,$interval){
/**
* Loads and populates the notifications
*/
this.loadNotifications = function (){
MyService.notification().then(function(data){
$scope.notification =data;
});
});
//Put in interval, first trigger after 10 seconds
var theInterval = $interval(function(){
this.loadNotifications();
}.bind(this), 10000);
$scope.$on('$destroy', function () {
$interval.cancel(theInterval)
});
//invoke initialy
this.loadNotifications();
}
This seems like a better architecture there.
Passing, resolving or rejecting promises will $digest the scope.
You want to get the notifications every x milliseconds and pass them into the scope.
You want to instantiate the timeout after it finishes. Try changing your $timeout function to something like:
var timer = $timeout( function refresh(){
$http.get("notification/get").success(callback)
timer = $timeout(refresh, 100000);
}, 100000);

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