find the cause of Error: [$rootScope:inprog] $apply already in progress - angularjs

My application has a dashboard with some panels, each one with a chart.(I use Angular-Chart.js) Each panel is a different custom directive.
I have TickService with a global tick, broadcasted each 1sec
function sendUpdateTich()
{
$rootScope.$broadcast("TICK_UPDATE_TIME");
$timeout(sendUpdateTich, 1000);
}
Each directive listens for the tick, to update their own graphics.
$scope.$on("TICK_UPDATE_TIME", function (event, data)
{
/* update chart's data */
// $scope.$apply();
});
Even if, by my own logic, I would use $scope.$apply() to refresh the directives' charts, I commented it out since it's useless, charts are already refreshed by them selves.
Anyway sometimes I've got this error:
Error: [$rootScope:inprog] $apply already in progress
http://errors.angularjs.org/1.5.3/$rootScope/inprog?p0=%24apply
http://localhost:8100/lib/ionic/js/ionic.bundle.js:13443:32
beginPhase#http://localhost:8100/lib/ionic/js/ionic.bundle.js:30758:31
$apply#http://localhost:8100/lib/ionic/js/ionic.bundle.js:30498:21
http://localhost:8100/js/orecchioDirective.js:25:40
$broadcast#http://localhost:8100/lib/ionic/js/ionic.bundle.js:30723:33
stopCheckinProximity#http://localhost:8100/js/callTimeService.js:226:26
stopChecking#http://localhost:8100/js/callTimeService.js:163:25
setEnded#http://localhost:8100/js/statusCallService.js:62:34
endCall#http://localhost:8100/js/controllers.js:319:31
fn
http://localhost:8100/lib/ionic/js/ionic.bundle.js:65429:21
$apply#http://localhost:8100/lib/ionic/js/ionic.bundle.js:30500:30
http://localhost:8100/lib/ionic/js/ionic.bundle.js:65428:19
defaultHandlerWrapper#http://localhost:8100/lib/ionic/js/ionic.bundle.js:16792:15
eventHandler#http://localhost:8100/lib/ionic/js/ionic.bundle.js:16780:23
dispatchEvent#[native code]
triggerMouseEvent#http://localhost:8100/lib/ionic/js/ionic.bundle.js:2953:20
tapClick#http://localhost:8100/lib/ionic/js/ionic.bundle.js:2942:20
tapMouseUp#http://localhost:8100/lib/ionic/js/ionic.bundle.js:3018:13
It doesn't occur at every tick, but just sometimes, I think (I don't know, but I just think..) according to some other $broadcasted event. I don't have any $scope.$apply, $rootScope.$apply neither $scope.$refresh, anywhere in the code. What's the problem?
[UPDATE 1]
According to this, $timeout() does the $apply() call for us so we don't have to. Since I have others $timeout() in Services, is it possible that they messy together?

Related

"$watch"ing a service doesn't appear to update reliably

I am currently building a simple drag & drop directive so that I can move some SVG stuff around on the screen. At the moment I'm still in the early stages, but I have run into a strange issue with $watch that I'm hoping someone can help me with.
I have a service that maintains my mouse state. At the moment it's just the x and y coordinates of the cursor. I also have an attribute level directive that interacts with this service in order to bind to the mouse-move event and update the service whenever someone moves the mouse around. These two items work together like a champ. The directive keeps the service up to date with the mouse's position and since my service (Factory really) is a singleton, I can pull this data in to other directives/controllers to see what's going on with the mouse.
Here's the problem: I'm trying to allow a specific SVG element to be dragged around, so I created a super simple controller with two functions: a "trackDrag" function that begins tracking and moving a specific element, and a "releaseDrag" which stops tracking/moving the element (drops it where it is, basically).
Inside of my trackDrag function, I attempt to use $scope.$watch to watch the mouse service's current x and y coordinates. Since it's a factory, these values are returned in a function and my watch looks something like this:
$scope.$watch("mouseTrackingService.get()", function(){
// do some stuff here
});
This watch DOES fire off when I first start dragging an element but it doesn't fire as I continue dragging it across the screen. In my "releaseDrag" function, I deallocate the watcher and that seems to work correctly. I'm kind of stumped about why I don't see the watch fire off continuously, even though I can console write out inside of the service and I see that IT is updating correctly.
I've included a plnkr with some sample code below:
http://plnkr.co/edit/g3WEgiQWvd9oXCpFEByn?p=preview
If I just give in and use a $interval then this code works (updating the position every 10ms for example), but really I see that as a much less "angular" way of doing things vs binding.
Ugh, I'm just being dumb. I forgot that $scope.$watch can ONLY WATCH SCOPE VARIABLES.
I fixed this issue by adding the following wrapper around the service:
$scope.currentMouse = function(){
return mouseTrackingService.get();
};
I can then watch currentMouse:
$scope.$watch(currentMouse(), function(){
updateMousePosition(target);
console.log("noticed a change");
});
Of course that gives me that awful Digest error after more than like a half second of dragging:
Uncaught Error: [$rootScope:infdig] 10 $digest() iterations reached. Aborting!
But that's a different issue entirely.
Sorry folks, nothing to see here. Move along now :-p

Angularjs view not reflecting the data retrieved from pouchDB (in browser database)

Background:
I am building my offline application which uses AngularJS for UI and PocuhDB for locally storing the data retrieved from the server.
Issue:
The data retrieved from PouchDB is not getting rendered in the UI.
Controller:
$scope.retrieveView = function (sys, code, majorVer, minorVer) {
var promise;
promise = dataService.getDataFromLocalDb().then(
function(dataFromPouchDb){
$scope.data = dataFromPouchDb.data;
});
return promise;
}
And then in the UI code I have the following :
<h1> {{data}}</h1>
I have debugged the code and everything seem to work fine. But the data is not getting displayed in the UI.
If I hard code a value to the data field then its getting rendered in the UI
$scope.data ="TEST";
This question is kind a old but I just came around it.
Issue is that Angularjs is based on so called digest cycles. When your model or view is changed digest cycle is triggered, watch for changes and update model or view respectively. It is so called two way data binding.
This digest cycle is not triggered periodically on some time base but on events instead. Those events are angular directives like ng-click, ajax calls $http or some other angular events like $timeout. You can find more information about digest here.
In general you should use those things when working with angular application to avoid such situations. In some cases its not possible however like in your case when getting data from DB. Digest cycle is not triggered and your view is not updated by angular.
Workaround for this is manually trigger $digest cycle. Way you have described:
if(!$scope.$$phase) {
$scope.$digest();
}
is working but considered as angular anti-patern and is discouraged by angular team, you should use:
$timeout();
instead. For more information see this answer.
I would maybe consider adding $timeout() call to hook for insert, update, delete hooks or events. Maybe pouchDB sync could be helpfull there.
The code you show seemed correct, maybe you can use console.log() to track the progress of the data. I think the problem might not in this layer. Maybe in the area where you wrapped getDataFromLocalDb(), track and find if the data have transfer to here, or where it disappeared.
The code started to work when i added the following :
if(!$scope.$$phase) {
$scope.$digest();
}
But i have no idea what magic does this code do.
It would be a great help if some some could advice.
The complete code that works now is :
$scope.retrieveView = function (sys, code, majorVer, minorVer) {
var promise;
promise = dataService.getDataFromLocalDb().then(
function(dataFromPouchDb){
$scope.data = dataFromPouchDb.data;
if(!$scope.$$phase) {
$scope.$digest();
}
});
return promise;
}

How does $timeout cause the afterSelectionChange function to be called?

I'm trying here to get enough info to go fix this problem, just wanting some help understanding what is going on inside angular.
ng-grid has issues, lots of them, but I've found a "fix" to this one that I don't understand.
I have a grid with enough rows that it fills the visible area. If I click on the different rows, the afterSelectionChange method is called. If after clicking in the grid I move the focus with the arrow keys, it only calls that callback if the grid scrolls.
So I put in a $timeout to print out the selected row every half second to see if it was changing the selected row and just not calling the callback, and THAT fixed the problem. Now every time I move the cursor with the keyboard, the callback fires, even though the only thing happening in the callback is $log.debug().
Is this because $timeout is causing something to happen within the framework like a $apply or a $digest?
If that's the case, why isn't the keyboard causing that to happen?
Edit: Options for #tasseKATT
$scope.callGridOptions = {
data: 'callRecords',
multiSelect: false,
sortInfo: {fields:['startOn'], directions:['asc']},
columnDefs: [ ...
],
afterSelectionChange: $scope.onCallChange,
selectedItems: $scope.selectedCalls
};
In the end, I could reduce the timeout code to this:
function ngGridFixer() {
// Presence of this timer causes the ngGrid to correctly react to up/down arrow and call the
// afterSelectionChange callback like it is supposed to.
$timeout(ngGridFixer, 500);
}
ngGridFixer();
I put this in the rootscope because the problem happens on all the pages of the app.
$log is part of the Angular framework, anything processed by it is might execute watches laid down earlier. In other words by calling $log.debug() to print out the structure, you might be basically running scope.$digest every half second, which cause the callback(s) to fire. If you take out everything inside the $timeout function, or use console.log instead, the callback(s) probably won't fire
A way to do this semi-properly would be to use something like ngKeydown.
EDIT:
$timeout execute the function in scope.$apply by default. https://docs.angularjs.org/api/ng/service/$timeout (invokeApply). I was not aware of this. So essentially your code is calling scope.$apply every half second.

How come Angular doesn't update with scope here?

I'm pretty new to Angular and I'm using firebase as my backend. I was hoping someone could debug this issue. When I first go to my page www.mywebsite.com/#defaultHash the data doesn't load into the DOM, it does after visiting another hash link and coming back though.
My controller is like this:
/* initialize data */
var fb = new Firebase('https://asdf.firebaseio.com/');
/* set data to automatically update on change */
fb.on('value', function(snapshot) {
var data = snapshot.val();
$scope.propertyConfiguration = data.products;
console.log($scope.propertyConfiguration);
console.log("Data retrieved");
});
/* save data on button submit */
$scope.saveConfigs = function(){
var setFBref = new Firebase('https://asdf.firebaseio.com/products');
setFBref.update($scope.propertyConfiguration);
console.log("configurations saved!");
};
I have 3 hash routes say "Shared", "Registration", and "Home" with otherwise.redirectTo set to "Shared".(They all use this controller) Here's the error that occurs: (all "links" are href="#hashWhereever")
1) Go to website.com/#Shared or just refresh. Console logs $scope.propertyConfiguration and "Data Retrieved". DOM shows nothing.
2) Click to website.com/#Registration, console logs $scope data properly, DOM is loaded correctly.
3) Click back to website.com/#Shared, console logs $scope data properly yet this time DOM loads correctly.
4) Refresh currently correctly loaded website.com/#Shared. DOM elements disappear.
Since $scope.data is correct in all the cases here, shouldn't Angular make sure the DOM reflects the model properly? Why is it that the DOM loads correctly only when I am clicking to the page from another link.
I can "fix" it by adding window.location.hash = "Shared" but it throws a huge amount of errors in the console.
FIXED:(sorta)
The function $scope.$apply() forces the view to sync with the model. I'd answer this question myself and close it but I'm still wondering why the view doesn't load correctly when I correctly assign a value to $scope. If Angular's "dirty checking" checks whenever there is a possibility the model has changed, doesn't assigning a value to $scope overqualify?
Angular has no way to know you've assigned a value to $scope.variable. There's no magic here. When you run a directive (ng-click/ng-submit) or Angular internal functions, they all call $apply() and trigger a digest (a check of the dirty flags and update routine).
A possibly safer approach than $apply would be to use $timeout. Currently, if you call a write op in Firebase, it could synchronously trigger an event listener (child_added, child_changed, value, etc). This could cause you to call $apply while still within a $apply scope. If you do this, an Error is thrown. $timeout bypasses this.
See this SO Question for a bit more on the topic of digest and $timeout.
This doc in the Angular Developer Guide covers how compile works; very great background read for any serious Angular dev.
Also, you can save yourself a good deal of energy by using the official Firebase bindings for Angular, which already take all of these implementation details into account.
Vaguely Related Note: In the not-too-distant future, Angular will be able to take advantage of Object.observe magic to handle these updates.

AngularJS $http.post responses activated much faster if periodic $rootScope.$digest in effect

In our very large and quite complex AngularJS application, I noticed (by accident!) that a line like this in my main module setup...
application.run(function($rootScope) {
window.setInterval( () => { $rootScope.$digest(); }, 1000);
});
...has significant positive impact, in the activation time of our $http.post requests.
A small part of our code that deterministically reproduces the behaviour is this:
// In the partial
<button ... ng-click="buttonHandler" ...>
// In the controller
$scope.buttonHandler = function() {
$http.post(....).success( function() { console.log("Activated"); })
}
We associate a button's ng-click handler with invocation of one of our web services.
The web service itself responds within 30ms (according to Chrome developer tools).
However, the code inside the .success handler is executed after 1.75 - 2.3 seconds (and only then is the message "Activated" displayed in the console).
However, when we put the Eternal rootScope $digest Hack (TM) in place, the activation occurs in less than a second! :-)
I can only guess that the rootScope $digest somehow 'triggers' the actual invocation of the .success handler, and since the webservice itself responds in 30ms, the activation time depends on when the button press happens to fall in the 1 second period of the hack's setInterval.
I am not saying that this is a recommended practise - but i was very surprised to see it happen. Note that there are no console errors logged or any other mischief reported anywhere in my assertive checks - the code works fine with or without the hack, but with significantly improved performance when the hack is in place.
Any idea what is going on?
Promises in Angular queue up a callback with $rootScope.$evalAsync(callback).
The callback checks to see whether the promise has been resolved/rejected.
$evalAsync schedules the task to run after the next digest cycle is complete.
Therefore, if you're preemptively asking for a digest cycle, your promise may be resolved quicker.
Source: https://github.com/angular/angular.js/blob/master/src/ng/q.js#L175

Resources