I have written a directive which has a two way binding to bring in the text specification for a flow chart which is used to generate the actual objects (steps and connections) inside the directive. I have a $watch set up inside the directive to allow the controller to send in a new specification (e.g. loading a new chart), but I also want to have the directive make changes to the specification in response to user action (e.g. deleting a connection) so that the controller can save the changed specification.
I have a function in the directive which converts the chart objects back into a text specification, but if I simply replace the specification scope variable with the updated value, the original watch sees a change (which it thinks might have come from the controller) and so reloads the chart from the specification. This has the effect of breaking things like dragging elements around because the elements are being removed from the DOM and replaced by new ones.
What I would like to be able to do is temporarily suspend the $watch while I make my internal changes, or in some other way avoid the watch from triggering when the directive makes changes to it, only going off when the controller makes changes from outside. I tried to unbind and rebind the watch around making the change, but because the actual checking happens elsewhere in the cycle that does not work. An alternative solution I could use is to have two variables passed between the controller and directive, one going each way, but that is somewhat inelegant. Any better suggestions would be welcome.
When I do this, usually my $watch looks something like this:
$scope.$watch('MyVar',function(newval,oldval) {
if (oldval == newval) return;
if (newval == $scope.internalval) return;
// process here
})
For precisely the reason you outline. I don't believe you can turn the $watch off, so before I make an internal update to the watched variable, I update a tracking version of the variable to make sure I don't get infinitely-recursing changes. It seems like a pain, but the watched variables are finite and I always use a setter function so the code updating the internal value is only written once
Related
I think I found a hard requirement to change behavior in a beforeChange handler based on the bindingContext of where a change is coming from. I already made some changes where a beforeChange handler gets to see the newValue along with the oldValue and where the handler can return boolean false to prevent the change from going forward.
The reason for this is the same objects may be bound to two (or more) different html nodes. The parent node may use different observables to wrap the same model object. But at some point, the child nodes are bound to properties through the same observables, sharing the same observable in more than one view element. When a change happens, I need to know from which of the two view elements the change originated.
I can save the bindingContext where the observable was made in the closure of my callbacks. But now I need the event to tell me what the bindingContext of the element was which just initiated the value change.
I can see the binding context somewhere in this domData thing, as follows: the valueUpdateHandler is called with the event object as argument it doesn't care about. But the event.target is our element that originated the change. I can see on that event target something like: "__ko__1655641582113" which I guess I am supposed to access with this ko.util.domData.
ko.utils.domData.get(arguments[0].target, "1__ko__1655641582113")
and lo and behold here I get an object with {alreadyBound: true, context: ko.bindingContext}
So I could force my way into this secret place and then get that context
ko.utils.domData.get($0, "1__ko__1655641582113").context.$parentContext.$rawData
and from there I could tell if I am in the element that should go forward with the change or not.
I am sure if anybody read this far that you'd be puzzled asking why in the hell I want to do that. But think about it, the observables are cool and all, but you sometimes need different behaviors based on where in your app the user is. In some areas they are just supposed to view, in others they can make (controlled) updates.
In other words, I know what I want is right and just, but I am wondering why it is so hard to get to what I want. For example, why the domData property is such a cryptic thing with a (timestamp) instead of a constant predictably named property. It's as if to tell me that I am making a big mistake even going there...
I'm creating a simple Angular app that uses Ace editor (via ui-ace) for on-screen text editing. I want a handler to run when the cursor changes which will update a model object when the cursor is in a certain location. However, I also want to be able to click a button to move the cursor to a particular location (and also update the model object if necessary). Here's a jsfiddle that demonstrates the idea.
http://jsfiddle.net/fpzknzej/3/
The model object updates when the cursor is placed at the end of the word print on the second line. The problem is that the $scope.$apply() on line 30 will throw an in-progress error when the Move Cursor! button is pressed. However, without that line, the view bound to the model object will not update when the cursor is moved with the arrow keys.
My current understanding is that this is simply the wrong way to do this kind of thing and that I need to do something along the lines of wrapping the changeCursor event to operate solely in the angular world. However, I'm at a loss as to how to approach this task (custom directive seems to be the thing that comes up the most when searching for this type of thing?) and if there's a good resource for understanding how to interact with third-party event handlers within angular. Any pointers in the right direction would be appreciated.
Will try to make this as simple as possible.
All angular core event directives such as ng-click , ng-change etc will automatically call $apply() internally.
In your case with move cursor you start the digest with ng-click then have a bit of a circular issue where the external ace event triggered from within ng-click is also going to call $apply().
You really only want to call $apply() when events that change scope outside of the angular core need to update the view.
A short term workaround for your situation is to use $timeout() instead.
This will be added to the end of the current digest stack queue and will call $apply() once other digests are completed.
As for directive , yes, this code does belong in directive but you won't really be changing it's current structure ... just moving it to a different part of the app. For the short term that isn't going to change what is currently happening
I have a simple table of data, created using AngularJS. One of the columns of the table is calculated from a function on the controller.
I have a button on the page that opens a new modal. When I open a modal using UI bootstrap, I get a new isolated scope (child of the root scope), as expected. If, however, I have an input text in the modal any key-presses in this text field automatically invoke functions on the parent scope - even though I can verify that the scope is isolated.
Here is a plunkr of the behavior: http://plnkr.co/edit/JzhxSDcSefDe04Psxq0w
As shown in the example, the third column of the table is calculated with a function called "ageNextYear". When the table is being rendered, this function is called many times as expected (and can be verified in the console log). If however, I open the modal and type some text in to the field, the "ageNextYear" function on the parent scope still gets called (type some text in the input field and watch the console log output).
I'm not sure whether this is intended behavior, or whether I'm doing something wrong. I have tried using dot notation on both scopes, and explicitly passing a new scope to $modal.open, but with no joy.
I can get around the problem (by creating a watchCollection on "people" and updating the table that way - which may be a better way of doing this overall) but wanted to validate whether others have seen this behavior also.
The issue you are experiencing is not related to the scope of the Modal Dialog. The issue is related to the use of a function within an ng-repeat expression. In general, using functions within expressions is a performance issue, but it's a much larger problem within an ng-repeat. according to This excellent article regarding common pitfalls of using scopes,
When using expressions in views or watchers, you should always remember that an expression is called every time AngularJS thinks it is needed. You will not get the best performance using functions, you might even miss some change events.
That means an expression…
within a ng-repeat will be called for each item separately. Additionally, this is used by the repeat directive to determine data changes.
Can be evaluated multiple times in one digest. This can happen when you're using multiple directives or additional scope watchers.
Can be evaluated even if the direct scope seems to be unchanged.
Containing a function will not be evaluated if the return value of the function changes, but only if the function definition has changed.
Your example causes 3 of these 4 to occur.
You repeat the function call for each object in your scope, 3 items = 3 calls to the function.
You add an additional watcher indirectly by calling the Modal Dialog.
Changes to the data in the Modal Dialog's scope causes evaluation of the scope of the controller containing the ng-repeat, even though the data within the ng-repeat didn't change (no way for it to know if the data changed until the $digest is called). Each change to the Modal causes the $digest, which causes another trip through the ng-repeat, and another call for each item in the ng-repeat.
In your case, the logic does not need to run every time the expression will be evaluated. It is better to compute and write the logic into the scope when the logic result has changed. This decouples the logic from the object and the view.
in summary,
Best practices:
DO NOT use functions in expressions.
DO NOT use other data besides the scope in an expression.
DO use $scope.$apply() when applying external data changes.
Simon,
I liked your question and I added watch on the scope and saw the digest cycle is getting called
$scope.$watch(function watchMe(scope) { console.log('Digest watched me!'); });
The following is the fork with the digest.
http://plnkr.co/edit/5PTO1uPFvmLrg7K9vzTm?p=preview
I donot know this is the reason but I think expressions inside the ng-repeat are calling the digest as it tries to evaluate expression on any event on that item.
I think we should evaluate expressions in the model and give the updated model to the ng-repeat to solve the issue.
Can anyone suggest how do I create a service to over ride angular js default $watch and $digest loop?
My requirement is to $watch a scope object with a listener and the listener function should have 4 parameters, #param1 - newObject, #param2 - oldObject, #param3 - Exact attribute which changed, #param4 - the heirarchy of the attribute in that object to that attribute which changed.
required :-
$scope.$watch('objectName', function(param1,param2,param3,param4){
//log param3 and param4 in a stack for later use.
//Note:- I dont want to log entire changed object since its too big.
}, true);
NOTE:- Right now we can deep watch an object but the listener would provide us with 2 objects, old object and new object. All I want is to get the exact attribute changed.
Any help will be highly appreciated. Please refer the below link for my complete problem.
Undo Redo In Angular JS
I think you can probably create an undo system that uses some sort of classification system for various types of undo along with a more event oriented approach. I focused on comments you made about iterating whole objects for each keystroke so will start there
I'm assuming that you aren't updating server for every keystroke on inputs in which case you wouldn't need to watch anything other than the specific input's key events while it is in focus. When you bind CTRL +Z to the document, do it in a way that nothing global happens when an input is in focus.
Following scenario is based on simplest UI action to track...update field only.
On focus of an input you would immediately take a copy of it's current model value, and store that as value to use for a complete undo of the ng-model later.
While in focus, CTRL+Z would be bound to that input and keystrokes events can be undone at very local level not impacting any need to watch at higher level. Unbind key events for that input on blur.
On blur of an input, you would then be able to $emit an update event that you would track as category like "field update". Send the old stored value , the object that contains the ng-model and update category to a tracking service that listens for updates and pushes updates into an undo array.
When user now hits undo, service looks at update type and changes the value of the object associated to the field name stored and removes that last stored item from the queue
I did something similar to this using only jQuery for a UI that was set up to both create and manage a web page layout with absolute positioned elements throughout, as well as inserting and updating content.
I've only started on user level input that assumes no new data objects created or deleted. Those might be handled by $.watch watching array lengths. By only watching length you wont have to iterate over every property of every object contained to figure out what changed.
One helpful tracking property for all of this is the $$hashKey property that angular gives to every object it touches in all scopes. Using haskey could help map array length changes to find differences. I haven't though all of this section through a lot yet.
Hope the event driven input tracking concept helps. At least at user input level it would cut out a significant amount of overhead using $watch
Also angular recently added a shallower listener $watchCollection was added in 1.2. Haven't used it yet, but surely lightens overhead where it's use case might not warrant a full watch
I'm still running into the same problem, filters and functions inside ng-repeat being called all the damn time.
Example here, http://plnkr.co/edit/G8INkfGZxMgTvPAftJ91?p=preview, anytime you change something on a single row, someFilter filter is called 1000 times.
Apparently it's because any change on a child scope bubbles up to its parent, causing $digest to run, causing all filters to run(https://stackoverflow.com/a/15936362/301596). Is that right? How can I prevent it from happening in my particular case?
How can I make it run only on the item that has changed?
In my actual use case the filter is called even when the change is not even on the items of ng-repeat, it's so pointless and it is actually causing performance problems..
// edit cleared all the unnecessary stuff from the plunker
http://plnkr.co/edit/G8INkfGZxMgTvPAftJ91?p=preview
This is just how Angular's dirty checking works. If you have an array of 500 items and the array changes, the filter must be reapplied to the entire array. And now you're wondering "why twice"?
From another answer:
This is normal, angularjs uses a 'dirty-check' approach, so it need to call all the filters to see if exists any change. After this it detect that have a change on one variable(the one that you typed) and then it execute all filters again to detect if has other changes.
And the answer it references: How does data binding work in AngularJS?
Edit: If you're really noticing sluggishness (which I'm not on an older Core 2 Duo PC), there are probably a number of creative ways you can get around it depending on what your UI is going to be.
You could put the row into edit mode while the user is editing the data to isolate the changes, and sync the model back up when the user gets out of edit mode
You could only update the model onblur instead of onkeypress using a directive, like this: http://jsfiddle.net/langdonx/djtQR/1/