Override $digest and $watch in Angular JS - javascript

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

Related

knockout.js change behavior of observable subscribers based on where a change event originates from

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...

AngularJS suspend $watch for internal updates

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

Best practice to maintain cursor position when keyup triggers save

I'm using Backbone.js. In my view, I have a textarea whose keyup is bound to a function like this (but see edit below):
this.model.save({text: self.$('textarea').val()}, {patch: true});
In the view's initialize function, I bind the model's change event to the view's render function:
initialize: function() {
this.listenTo(this.model, 'change', _.bind(this.render, this));
},
Trouble is, when the user types in the textarea, the following sequence of events occurs:
The keyup event fires.
The keyup handler calls save on the model.
The call to save triggers the model's model's change event.
The view, listening for the model's change event, calls render.
The textarea is replaced in the DOM.
The textarea is no longer focused, and the text cursor position is lost.
What is the best practice for situations like this, where a texarea's keyup event needs to trigger a sync? Some options I have considered:
Don't bind change to render. Disadvantage: If the model data changes due to anything other than the user typing, the textarea doesn't automatically update.
Read and remember the cursor position at the beginning of render. Set the cursor position at the end of render. Disadvantage: Depends on cursor manipulation features for which browser support is spotty.
In the keyup handler, set a temporary property on the view telling it not to re-render. Unset it after the model has been saved. Disadvantage: Feels like spaghetti code, fights against the structure of Backbone.
Are there any options I'm not seeing? Do you recommend one of the options above?
Edit:
I didn't want to distract from the main point, but since it came up in one of the answers: I'm not binding directly to keyup, but intermediating it with _.debounce. Thus, the event handler only runs once the user stops typing, as defined by a certain amount of time elapsing since the last keyup.
First of all I'd like to discourage this as it seems like really strange behaviour to save your model on keyup. If there is a use-case which really necessitates this I'd suggest using the input event at the very least - otherwise you'll end up saving the model every time the user presses even an arrow key, shift, ctrl etc.
I think you'll also want to debounce the input events by 500ms or so you're not actually saving the model every single keystroke.
To address your comment in point 1:
Disadvantage: If the model data changes due to anything other than the
user typing, the textarea doesn't automatically update
You need to ask yourself the likelihood of this happening and how important it is that the view is rerendered if this was to happen.
Finally, if you decide that this is indeed likely and it is important that the view is rerendered, then you can try something like this
http://jsfiddle.net/nuewwdmr/2/
One of the important parts here is the mapping of model attribute names to the name field of your inputs. What I've done here follows the sequence of events you described above. The difference is that when the model changes, we inspect the changed attributes and update the value of the corresponding element in the template.
This works fine in a very simple situation, the happy path, where the user is typing in a "normal" way into the input. If the user, however, decides to go back to the start of the input and change a letter to capitalize it, for example, the cursor will jump to end of the string after the change event in the model occurs.
The behaviour you require here is really two-way data-binding which is by no means trivial, especially with Backbone given just how little functionality a Backbone View has.
My advice would be your point 1
Don't bind change to render
Edit
If you want to look further into model / view binding you could take a look at two libraries:
stickit
epoxy
I've used stickit before and it's...fine. Not great. It's ok for simple bindings, for example binding a "top-level" model attribute to an input element. Once you get into nested attributes you'll run into problems and you'll then have to look into something like Backbone Deep Model.
Like I said, Backbone's View doesn't offer very much. If you've got the time I'd suggest looking into using React components in place of Backbone Views, or even look at some of the interesting stuff that ampersand have to offer.

Angular.js change on one item of ng-repeat causing filters on all other items to run

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/

Knockout dirty flag event

I've used the Dirty Flag Blog post here Knockmeout to implement such a flag in my model, but i can not get this to work properly. Somehow the flag is never set to true.
Additionaly i want my subscribe event to be triggered every time the dirty flag is set to true. (i'll to the reset manually).
Here's a fiddle that shows my issue.
Can someone point me in the right direction?
A couple of small things:
when you use span tags they should not be self-closing (so do <span></span>). This was preventing your final binding from being shown.
if you create your view model inside of an object literal, then this does not yet refer to the view model, so when you created your dirty flag it was not properly tracking your Filter object. If you want to do an object literal, then you would want to create your dirty flag afterwards.
the value binding when used with a select will populate your model value with a string. So, I changed your 1 to '1', otherwise it would be dirty immediately. There is a way to force it to be numeric using a writeable computed observable. Here is one technique.
Here is an updated sample: http://jsfiddle.net/rniemeyer/xw76d/4/

Categories

Resources