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

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.

Related

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

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?

"$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

Need explanation on a angular direcive load

I just want to understand why in the following jsFiddle 'here is a lo' is printed three times.
http://jsfiddle.net/wg385a1h/5/
$scope.getLog = function () {
console.log('here is a log');
}
Can someone explain me why ? What should I change to have only one log "here is a log" (that's what I would like this fiddle do). Thanks a lot.
Angular uses digest cycles/iterations to determine when state has changed and needs to update the UI. If it finds any change on one of it's cycles, it keeps rerunning cycles until the data stabilizes itself. If it's done 10 cycles and the data is still changing, you'll see a rather know message: "angularjs 10 iterations reached. aborting".
Therefor, The fact that you are seeing the message displayed 3 times is because you have a simple interface. In fact, you can get up to many more such messages in the log, due to the fact that your directive uses {{getLog()}}. Angular keeps evaluating the expression to see if it changed.
To avoid such problems, under normal circumstances, you should store the value returned by the function you want called only once in the $scope object inside the controller and use that variable (not the function call) in the UI.
So in the controller you'd have $scope.log = getLog() [assuming it returns something, and not just writing to the console] and in the directive use the template {{log}}. This way, you'll get the value only once, per controller instance.
Hope I was clear enough.

ngGrid afterSelectionChange not firing

I have my ngGrid in a partial page, and I'm trying to access the selected items on the main page, I assume the best way to get this done is with $emit(to parent scope) and $on (to catch the event). For some reason my afterSelectionChange doesn't appear to be firing and I'm wondering if I'm just doing something wrong. Or if there's a better way to track changes to a child grid selection even.
I have
{{mySelectedItems}}
On my partial, and it updates just fine. But also have
{{selectedDevices}}
On my main view, and it's never getting updated.
A breakpoint on the function within the "afterSelectionChange" also is not getting hit. here's the gridoptions. zz is just my previously defined data, not really relevant, also chopped out my columnDefs to minimize space.
$scope.mySelectedItems = [];
$scope.gridItems = #Html.Raw(Json.Encode(zz))
$scope.gridOptions = {
selectedItems:$scope.mySelectedItems,
afterSelectionChange: function (data) {
$scope.$emit("selectionChanged", $scope.mySelectedItems)},
multiSelect:true,
data: 'gridItems',
enableColumnResize: true
};
And finally on my main View, I have
$scope.$on("selectionChanged",function(event,data){
$scope.selectedDevices=data;
})
to catch the changes.
Ok so apparently you're not able to set a breakpoint where I was trying, I added a debugger line and it successfully broke in the afterselectionchange. It was the emit that was not actually working correctly. but once I was able to successfully get into debug, i was able to trace the problem down to a scope issue. I was catching the event in the wrong parent scope.
It was a bit confusing for me at first because we are using razor #sections so the partial view was not being included in the same parent scope, it was parenting to the layout page.
I moved my ng-controller to a div surrounding the partial in that section and that solved it.
The lesson here... watch your scopes.

Display n time ago on various items using jquery, [issue with new elements generated after the loading the DOM]

I am using Jquery plugin http://timeago.yarp.com/ for showing time.
Issue is timeago will not take effect for dynamically generated items.
$(document).ready(function() {
$(".timeago").timeago(); // works perfectly fine for the items which are loaded on page load
//$(".timeago").live(timeago()); // gives me an error ie timeago is not defined
//$(".timeago").live($(".timeago").timeago()); // gives me an error too much recursion.
jQuery.timeago.settings.allowFuture = true;
});
From some google search I got to know something ie:
Using live is the same as using bind, except that it is limited only to the events click, dblclick, keydown, keypress, keyup, mousedown, mousemove, mouseout, mouseover, and mouseup.
Now how can do it cause I dont have any click event? How can I bind this?
.live() and .bind() assign callbacks to event. In your case, you don't have an event to assign the function to and so it fails.
You can, in theory, assign your callback to a custom event. You will however have to manually trigger the event (using .trigger()) whenever your item is generated. For example:
$("abbr.timeago").live("timeago", function() {
$(this).timeago();
});
// ... and in the bit that generates your item
$new_item.trigger("timeago")
Demo: http://jsfiddle.net/ZjuW4/9
Of course, using .live() in this situation is purely academic and does not really serve a good purpose.
If you do have access to the code that's generating the items, you can simply chain in the call to .timeago() as the items are generated, i.e. http://jsfiddle.net/ZjuW4/3/
take a look in this topic
here was discussed how to put timeago on dynamically loaded items
for example the results of an ajax request.
Activate timeago on newly added elements only
PS: allowFuture does not have anything to do with putting timeago on newly created items on your page. it just allows dates in the future (f.e. "in 3 days", "next week")

Resources