I have a user object, and I want to track changes in case the user edits their information, however they can say 'discard changes' and it returns to the previous state of the user object on load.
My solution was to deepClone the original object into a backup var, to give it it's own reference points, then compare the user object to the backup object --- again using lodash
I watch the object using angular #$watch, and if !_.isEqual(user, backup).
$scope.$on '$routeChangeStart', (e, next) ->
if !_.isEqual(user, backup)
console.log 'changes made'
However this returns false, even when there are no changes? It's as if the two objects are not equal any longer, even though all the keys and values are identical? I assume more happens than what I see when I deepClone.
Any better approaches? And what am I doing wrong?
The problem is most likely that user has been altered by angular. Angular adds some properties for tracking to objects bound to the view (these properties start with $ or $$). If your objects don't have such 'native' properties, you could try to do a deepOmit on user first.
This is a classic example when the concept of immutability would help a lot. If your object would be immutable a copy of it would only mean a pointer to the original object and comparisons are thereby more robust in my opinion. Check out https://facebook.github.io/immutable-js/ if you would like to know why facebook has embraced it (and maybe try using it yourself).
Related
Problem: I'm re-writing a function that returns a large plain javascript object. The original object should not be modified by callers. The function is currently called by hundreds of callers.
Some solutions:
Return a deep copy of the object. This is the current solution. This is bad because (a) the object is very large, and (b) 90% of the time the clone is not necessary because the majority of the callers do not modify the value.
Return a reference to the object. This is bad because callers will be able to modify the original object, which I do not want.
Return a reference with Object.freeze or something similar. This might work if I could guarantee that callers don't attempt to modify the return value. But I can't feasibly guarantee this. Again, the function is called by hundreds of callers, and many already modify the (clone of the) value.
What I want is a copy-on-write solution that has the following two properties:
The return value of the function is a reference to the original object until the value is modified.
After the value is modified, it becomes a deep clone of the original object.
I did some tests and I can implement this using Javascript Proxy. Simply trap on all operations that modify the target (i.e. set, deleteProperty, defineProperty, etc.) to listen for changes. Then trap on get to return a reference if no changes have been made, and return a clone if changes have been made. The proxy would also have to be a deep proxy to listen for changes on nested properties.
While I can implement this myself, I was wondering if there were any implementations already out there. I'd rather not re-invent the wheel if I don't have to. Browser compatibility is nice, but is not essential.
The immer library doing exactly what you describe - implementing COW using JavaScript proxies.
Take a look at the docs here: https://immerjs.github.io/immer/
We are building a form-based app that has a complex object with many levels of nested properties.
So far, I have created a simple experiment with a single view model with one object. The experiment has fields that are bound to object properties, which successfully display the data. However, when changing the fields, the object does not seem to be updated.
What should I do to make sure form input propagates throughout the view model and into the template?
You need to use getter method in app.js as below,
get swaggerString() {
console.log(this.swagger);
const swaggerStringified = JSON.stringify(this.swagger);
return swaggerStringified;
}
In your HTML, change method to property,
${swaggerString}
Updated your GIST,
https://gist.github.com/anonymous/3b85820d66c2dfbf0f770208a7c8b63f
Hope this helps!
What are you planning to do in the real app?
The accepted answer only answers how to solve your problem as posted in the gist. I'm guessing your real app doesn't need to display JSON data.
If you are just wanting to display deeply nested object properties, then that is simple, you simply bind to those properties themselves. See here: https://gist.run/?id=5af5c22be4b49c0e3fef327e3d8b986b
<pre>
{
"name": "${swagger.name}",
"version": "${swagger.version}"
}
</pre>
You can even go arbitrarily deep in to an object tree, e.g. ${foo.bar.baz.ball.foop}.
The thing to understand is that Aurelia observes for changes to whatever you tell it to observe. When you tell it to simply observe an property that is an object, it can only watch for changes to the property itself. This means it will only see a change if you assign a different object to the property. It does not watch every property on the object for changes for performance reasons (and also due to Object.observe being cancelled).
All hope is not lost, though. Please respond with some specifics and I'll try to help you out better.
I do not have time to create a fiddle right now, but will definetely do so tomorrow.
Basically my problem is caching a data model retrieved from a restful get endpoint and comparing to a new model returned by a restful updated endpoint in order to be able to highlight the changed values in the UI.
The way I handled this is by using underscore's each() and angular.compare() methods in order to loop through a collection and compare it object key by object key.
However this feels wrong and I have problems in getting the updated key name.
Is there a better, accepted way to do this as I cannot find anything anywhere, just a bunch of people generally asking the same question and getting answers like: 'Use a watcher and underscore/angular methods, it is easy'.
For what I have understand, what you are trying to do is the correct way to see it.
You have to compare object by object.
To help you between the old collection and the new one, a watcher is not a bad answer. In fact the angular watcher can give you the old collection and the new one as parameters. So all you got to do is make a check object by object. And had a special treamenton the different values.
Hope this can help you.
I have two objects, obj1 and obj2. I want one to have the same properties and values as the other when the user clicks a button, so I write this line:
main.obj1.data=$.extend({},main.someArray[0].data);
But now updating main.obj1.data automatically updates main.someArray[0].data and vice versa. I tested it by checking that this is true with console.logs immediately after that line of code. I thought that this command would clone the objects but not make them aliases of the same object. What am I doing wrong?
I have messy code to sort through before this command...is there anything I might have put in my code before that point which would cause $.extend to no longer work like I think it should?
There is two way to solve this
1.
main.obj1.data={};
$.extend(main.obj1.data,main.someArray[0].data);
2.
main.obj1.data=$.extend(true,{},main.someArray[0].data)
Actually both is doing the same thing
You can read more about $.extend()
jQuery extend copy the values so the copied object shouldn't be linked to the initial object. You can verify that in this jsfiddle: http://jsfiddle.net/cFtA7/ (open your console and run the script).
The first parameter in $.extend(true,{},main.someArray[0].data) serve to deep copy your object. If your object has many levels, use this parameter, otherwise, it's not needed.
I'm guessing, but is that possible that your extend code is on a bind object that is called every time you update either one of the values?
I have a question using underscore isEqual to compare two JSON strings. Currently i have done an app in backbone, and I'm using _.isEqual(savedModel.toJSON(),changedModel.toJSON() ) to detect if the model has changed in the page and promt a "You have unsaved changes, do you want to save?" dialog if the user tires to navigate away.
For some reason I get the dialog in random places even though I have done nothing or have saved changes. Debugging is driving me crazy.
Could this be because JSON does not guarantee the order of the objects in the JSON and underscores isEqual does not handle this case properly? So even if the models are the same, some attributes in the JSON might be different and it returns false?
Pseudocode:
//when entering the page the original model is cloned, when user does changes to the
//page, the model is cloned again
var savedModel = currentModel.clone().toJSON();
//when the user tries to navigate away from the page
if( _.isEqual(savedModel, model.toJSON() ){
showSavePromptDialog();
}
Following the chain of functions used by backbone.toJSON(), it appears _.extend is used to copy the object and _.extend uses a for..in loop to iterate over the object. for..in iterates over an object in arbitrary order, which is likely the source of your problem.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for...in
Hi this deep equals implementation done to solve similar problem, but I might have missed out some finer details, it was serving well for my purpose.
http://yui3.wordpress.com/2013/04/22/deep-compare-in-javascript/