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
Related
I am trying to test an Angular directive that uses the click event and getting nowhere. I can get the event to fire just fine, but PhantomJS2 just takes forever to make the event happen and I can't figure out a way to get Jasmine to wait for it to happen before running the assertion. I've searched quite a bit and the most common response is this: How to wait for a click() event to load in phantomjs before continuing?. Sadly, that guy hasn't figured it out either, and I'm not waiting for a page to load, just for the click event to finally register. I'm hoping that someone else has and just didn't see that question or didn't want to start a reply on such an old question. For reference, the directive in question selects the text within an input tag if there is none already selected. I've tried every permutation of timeouts that I can think of, but nothing works. The last attempt involved using the done() function from Jasmine, but still no luck.
//the default value in the input is "unchanged"
it('should not select anything if there is already a selection in the element', function(done) {
element[0].setSelectionRange(2, element[0].value.length);
element.triggerHandler('click');
scope.$digest();
expect(theWindow.getSelection().toString()).toEqual('changed');
done();
});
All reasonable suggestions will be considered. I'd be ecstatic if someone had a link to a solution that I somehow didn't find with my Google-foo.
Note: I got the idea about using setTimeout from here: https://newspaint.wordpress.com/2013/03/19/how-to-click-on-a-div-or-span-using-phantomjs/
I'm wrestling with the way angular watches arrays. I have the following markup:
<map name="theMap" center="{{mapInfo.center.toUrlValue()}}" zoom="{{mapInfo.zoom}}" on-dragend="dragEnd()" on-zoom_changed="zoomChanged()">
<marker ng-repeat="pin in pins() track by pin.iconKey()" position="{{pin.latitude}}, {{pin.longitude}}" title="{{pin.streetAddress}}" pinindex="{{$index}}" on-click="click()"
icon="{{pin.icon()}}"></marker>
</map>
Each individual pin returned by pins() has a number of properties, sub-properties, etc. One of those sub-properties controls the marker color. When that subproperty changes I want the UI to update.
Because ng-repeat appears to $watch based on simply changes to the collection it's not obvious how to achieve that. I thought that my tracking function, iconKey(), would do it because it returns a different value depending upon the subproperty's value. But that didn't work.
One other thing: the subproperty gets changed in the callback from an $interval that runs under a directive. I mention this because, in an earlier post, someone thought that there might be a context/scope problem.
However, even when I make the change in an event listener within the UI's controller (where the event is raised within the "success" clause of the $interval callback) it still doesn't work.
That's why I think the problem is just angular not noticing the change in iconKey(); it's acting like all it $watches for ng-repeat is the array's length. Which doesn't change when the subproperty changes.
Update
I've created a plunker to demonstrate the issue I'm facing. You can find it at http://plnkr.co/edit/50idft4qaxqw1CduYkOd
It's a cut down version of the app I'm building, but it contains the essential elements (e.g., a data context service to hold information about the map pins and an $interval service to toggle the subproperty of one of the pin array elements).
You start the update cycle by clicking Start in the menu bar (you may want to drag the map down slightly to put both pins into full view). It should toggle the color of each pin, alternatively, 5 times each, once every 2 seconds. It does this by toggling the value of the isDirty property of the pin object in a listener defined in the controller. The event is raised by the $interval service.
If you break on line 22 during the test cycle you'll see the pin's icon being returned. So something within angular is calling for the information...but the pin color doesn't change.
I look forward to someone quickly pointing out the bone-headed mistake that has nothing to do with any of my theories :). Apologies in advance for whatever blinders I'm wearing.
Update 2
After checking out the code snippet in the response I simplified my plnkr and demonstrated that angular is, in fact, updating the UI when a subproperty changes. So this appears to be a limitation or bug in ng-map.
What you are missing here is the concept of array and function your function pins() passes an array and that array is been bound with ng-repeat. But the brutal fact here is that no matter what that array is never changed, because you do not have ANY reference to that array hence the rg-repeat will always remain as is...
I'll suggest to try get the array be referenced two ways to do that
ng-init="pinsArray = pins()"
and second inside controller
$scope.pinsArray = $scope.pins()
then make changes to $scope.pinsArray inside controller
ng-repeat will be changed to
ng-repeat="pin in pinsArray"
also read about filters I am guessing that's what you where trying to do with "track by"
hope this helps..
Edit: different story with ngMap markers
seems like it doesn't watch sub-property.
so here's a work around
add following statement to you update the pinsArray after making changes to its properties.
pinsArray = angular.copy(pinsArray);
the solved plnkr example:
http://plnkr.co/edit/EnW1RjE9v47nDpynAZLK?p=preview
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.
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.
I'm converting a page in a mvc application with a lot of inline jquery javascript to angular using typescript.
The first calls works fine but I have a problem: based on a selection in a dropdown, the event updates several controls in the view, make a few ajax calls and finally update the view with the data from the calls.
Seems the conversion is working fine, but at the end of the call the page isn't updated.
I tried to remove all the old jquery code to avoid problems.
batarang and java console reports no errors.
the final ajax call is done and the result shown in a debug.
All seems to work fine, but the page isn't updated.
How can I find the problem?
thanks
Without seeing any code, it's difficult to answer but if you bind an event to an element and want to update something in the callback, you will have to use $apply
scope.$apply(function () {
// your code
});
$apply will trigger a $digest cycle, and should be used when you want to update something while being outside angular's context.
Most likely you are not handling your asynchronous calls correctly. It's impossible to tell from your question but it is a very common mistake with symptoms as you describe.
Make sure you are updating your model within the .then() method of a promise returned from an $http request. For example
someFnDoingHttpRequest().then(function(data){
// updated the model with the data
})
Also (another common mistake) make sure someFnDoingHttpRequest() returns a promise.
If you want to "find the problem" then you can use the following option.
Go to Internet Explorer (10 or 11).
Select "Internet Options" from the settings menu.
Go to the "Advanced" tab (the last tab)
Settings are listed and select "Display a notification about every script error"
Deselect the "Disable Script debugging (Internet Explorer)" and "Disable script debugging (Other)"
Run the program again, you will get notification about the real issue that happens while displaying actual result.