I want to $watch an object for changes on any of its properties and, when any of them changes, get its name (apart from newValue and oldValue).
Is that possible?
This question is two years old but I just ran into the same issue and I thought i'd share my solution to it. Back in the 1.2 version you used to be able to access the changed property by referencing this.exp in the watch function, but that was deprecated presumably for performance reasons. The easiest way to get the changed property is to loop through the new and old objects and compare the values by property name.
$scope.settings = {
'opt1' : 'something',
'opt2' : 'something else',
'opt3' : 'another something',
};
$scope.$watchCollection('settings', function(newObj, oldObj) {
// Loop through new object and compare
angular.forEach(newObj, function(val, key) {
if(newObj[key] !== oldObj[key]) {
// settings[key] changed
}
});
}, true);
No, it's not possible - the only information you get is the new and previous value of an watched object.
You can tell that by looking at the Angular digest loop implementation:
// I ommited most of the code, leaving only the relevant part
if ((watchers = current.$$watchers)) {
while (/* iterate over watchers */) {
// Compare the current value with the last known value
if ((value = watch.get(current)) !== (last = watch.last)) {
// Notify the callback - only the whole values (new and last) are supplied.
watch.fn(value, ((last === initWatchVal) ? value : last), current);
}
}
You could manually enumerate object properties and compare them, or you could use some third party library for that. Quick NPM search returned this: deep-diff (it is available through bower as well). If you do opt to use it, it could look like:
$scope.$watch('watchedObject', function(newValue, oldValue) {
var differences = diff(newValue, oldValue);
// now inspect the differences array to see if there are any
});
Watching an object is not possible, unless you know the variable name in the $scope.
Say you have an object you want to watch at $scope.myobject. You will iterate over the object properties:
var objprops = []
angular.foreach(myobject, function(value, key) {
objprops.push("myobject." + key);
});
objexpr = "[" + objprops.join(",") + "]";
//objexpr will be something like "[myobject.a, myobject.b, myobject.c, ...]"
Then you watch over this array:
$scope.$watch(objexpr, function(newv, oldv){
//do iteration here
});
The big caveat here is that, to detect the changed values, you must iterate the newv array against the oldv array and remember objprops to know which field actually changed.
It's not clean... at all
Related
So I'm in a unique situation where I have two objects, and I need to compare the keys on said objects to make sure they match the default object. Here's an example of what I'm trying to do:
const _ = require('lodash');
class DefaultObject {
constructor(id) {
this.id = id;
this.myobj1 = {
setting1: true,
setting2: false,
setting3: 'mydynamicstring'
};
this.myobj2 = {
perm1: 'ALL',
perm2: 'LIMITED',
perm3: 'LIMITED',
perm4: 'ADMIN'
};
}
}
async verifyDataIntegrity(id, data) {
const defaultData = _.merge(new DefaultObject(id));
if (defaultData.hasOwnProperty('myoldsetting')) delete defaultData.myoldsetting;
if (!_.isEqual(data, defaultData)) {
await myMongoDBCollection.replaceOne({ id }, defaultData);
return defaultData;
} else {
return data;
}
}
async requestData(id) {
const data = await myMongoDBCollection.findOne({ id });
if (!data) data = await this.makeNewData(id);
else data = await this.verifyDataIntegrity(id, data);
return data;
}
Let me explain. First, I have a default object which is created every time a user first uses the service. Then, that object is modified to their customized settings. For example, they could change 'setting1' to be false while changing 'perm2' to be 'ALL'.
Now, an older version of my default object used to have a property called 'myoldsetting'. I don't want newer products to have this setting, so every time a user requests their data I check if their object has the setting 'myoldsetting', and if it does, delete it. Then, to prevent needless updates (because this is called every time a user wants their data), I check if it is equal with the new default object.
But this doesn't work, because if the user has changed a setting, it will always return false and force a database update, even though none of the keys have changed. To fix this, I need a method of comparing the keys on an object, rather any the keys and data.
That way, if I add a new option to DefaultObject, say, 'perm5' set to 'ADMIN', then it will update the user's object. But, if their object has the same keys (it's up to date), then continue along your day.
I need this comparison to be deep, just in case I add a new property in, for example, myobj1. If I only compare the main level keys (id, myobj1, myobj2), it won't know if I added a new key into myobj1 or myobj2.
I apologize if this doesn't make sense, it's a very specific situation. Thanks in advance if you're able to help.
~~~~EDIT~~~~
Alright, so I've actually come up with a function that does exactly what I need. The issue is, I'd like to minify it so that it's not so big. Also, I can't seem to find a way to check if an item is a object even when it's null. This answer wasn't very helpful.
Here's my working function.
function getKeysDeep(arr, obj) {
Object.keys(obj).forEach(key => {
if (typeof obj[key] === 'object') {
arr = getKeysDeep(arr, obj[key]);
}
});
arr = arr.concat(Object.keys(obj));
return arr;
}
Usage
getKeysDeep([], myobj);
Is it possible to use it without having to put an empty array in too?
So, if I understand you correctly you would like to compare the keys of two objects, correct?
If that is the case you could try something like this:
function hasSameKeys(a, b) {
const aKeys = Object.keys(a);
const bKeys = Object.keys(b);
return aKeys.length === bKeys.length && !(aKeys.some(key => bKeys.indexOf(key) < 0));
}
Object.keys(x) will give you all the keys of the objects own properties.
indexOf will return a -1 if the value is not in the array that indexOf is being called on.
some will return as soon as the any element of the array (aKeys) evaluates to true in the callback. In this case: If any of the keys is not included in the other array (indexOf(key) < 0)
Alright, so I've actually come up with a function that does exactly what I need. The issue is, I'd like to minify it so that it's not so big. Also, I can't seem to find a way to check if an item is a object even when it's null.
In the end, this works for me. If anyone can improve it that'd be awesome.
function getKeysDeep(obj, arr = []) {
Object.keys(obj).forEach(key => {
if (typeof obj[key] === 'object' && !Array.isArray(obj[key]) && obj[key] !== null) {
arr = this.getKeysDeep(obj[key], arr);
}
});
return arr.concat(Object.keys(obj));
}
getKeysDeep(myobj);
I'm trying to assign the value of an array element to an object. After first attempting something like, e.g.bar = foo[0]; I've discovered that any change to bar also changes foo[0], due to having the same reference.
Awesome, thought no one, and upon reading up on immutability and the ES6 Object.assign() method and spread properties, I thought it would fix the issue. However, in this case it doesn't. What am I missing?
EDIT: Sorry about the accountTypes confusion, I fixed the example.
Also, I would like to keep the class structure of Settings, so let copy = JSON.parse(JSON.stringify(original)); is not really what I'm after in this case.
//this object will change according to a selection
currentPreset;
//this should remain unchanged
presets: {name: string, settings: Settings}[] = [];
ngOnInit()
{
this.currentPreset = {
name: '',
settings: new Settings()
}
this.presets.push({name: 'Preset1', settings: new Settings({
settingOne: 'foo',
settingTwo: false,
settingThree: 14
})
});
}
/**
* Select an item from the `presets` array and assign it,
* by value(not reference), to `currentPreset`.
*
* #Usage In an HTML form, a <select> element's `change` event calls
* this method to fill the form's controls with the values of a
* selected item from the `presets` array. Subsequent calls to this
* method should not affect the value of the `presets` array.
*
* #param value - Expects a numerical index or the string 'new'
*/
setPreset(value)
{
if(value == 'new')
{
this.currentPreset.name = '';
this.currentPreset.settings.reset();
}
else
{
this.currentPreset = {...this.presets[value]};
//same as above
//this.currentPreset = Object.assign({}, this.presets[value]);
}
}
Try this : let copy = original.map(item => Object.assign({}, ...item));
This will create a new object without any reference to the old object original
In case if you want to do this for an array try the same with []
let copy = original.map(item => Object.assign([], ...item));
You have to do a deep copy, this the easiest way:
let copy = JSON.parse(JSON.stringify(original));
This doesn't really answer the question, but since the object I'm trying not to mutate doesn't have nested properties within, I call the assignment at the property level and the shallow copy here is fine.
setPreset(value)
{
if(value == 'new')
{
this.currentPreset.name = '';
this.currentPreset.settings.reset();
}
else
{
this.currentPreset.name = this.presets[value].name;
this.currentPreset.privileges = Object.assign(new Settings(),
this.presets[value].settings);
}
}
A better solution, since I'm creating a new Settings() anyway, might be to move this logic to a Settings class method and call it in the constructor
I had the same problem recently, and I could not figure out why some of my objects were changing their properties. I had to change my code to avoid mutation. Some of the answers here helped me understand afterwards, such as this great article : https://alistapart.com/article/why-mutation-can-be-scary/
I recommend it. The author gives a lot of examples and useful libraries that can outperform Object.assign() when it comes to embedded properties.
I have an issue that I am struggling to grasp. Any help would be greatly appreciated.
I have an Object, and I assign the current object state to a property on the current object.
example below:
var product = {
ropeType: 'blah',
ropePrice: 'blah',
ropeSections: {
name: 'blaah',
price: 'blaah'
},
memory: false
}
product.memory = product;
Now when I look at the product object within the console I get a inifinite recursion of Product.memory.Product.memory.Product....
screenshot below:
I know its something to do with that an object references itself, but I cannot seem to grasp the concept. Could someone explain?
The reason I am trying to do something like this is to save in local storage the current state of the object.
I hope I have made sense.
I assign the current object state to a property on the current object.
No, you created a property that referred to itself.
If you want to save the current state of the property then you need to clone the object.
If you want to create a (shallow) copy of an object then you can use:
function clone(obj) {
if(obj === null || typeof(obj) !== 'object' || 'isActiveClone' in obj)
return obj;
var temp = obj.constructor();
for(var key in obj) {
if(Object.prototype.hasOwnProperty.call(obj, key)) {
obj['isActiveClone'] = null;
temp[key] = obj[key];
delete obj['isActiveClone'];
}
}
return temp;
}
[code taken from here - and modified slightly to do a shallow copy rather than recursive deep copy]
then do:
product.memory = clone( product );
You may find you get the issues with recursion if you clone it a second time and it copies the product.memory along with the rest of the object. In that case just delete product.memory before doing subsequent clones.
Something like:
function saveCurrentState( obj ){
if ( 'memory' in obj )
delete obj.memory;
obj.memory = clone( obj );
}
Aside
If you want a deep copy then you can do:
function deepCopy(obj){
return JSON.parse(JSON.stringify(obj));
}
[As suggested here - but note the caveats it has for Date objects]
you could do your idea by clone the current product into new. We've Object.keys to get all attribute of object. So here is my idea :
product = {
ropeType: 'blah',
ropePrice: 'blah',
ropeSections: {
name: 'blaah',
price: 'blaah'
},
memory: false
};
var keys = Object.keys(product);
var newProduct = {};
keys.forEach(function(key){
if(key === 'memory') return;
newProduct[key] = product[key];
});
product.memory = newProduct;
Instead of actually storing a reference to the object, you might want to transform that object's state. Maybe by cloning it onto a new object or possibly keeping it as a JSON string (which you'll want to do if you're using localStorage).
Since you will probably want to see the current state of the object whenever you check the memory property, you should make memory a function that does that transformation.
Maybe something like this:
var product = {
ropeType: 'blah',
ropePrice: 'blah',
ropeSections: {
name: 'blaah',
price: 'blaah'
},
memory: function() {
return JSON.stringify(this);
}
}
You can then call product.memory() and get its state in JSON.
This here is the problem:
product.memory = product;
You're assigning a reference to an object to itself. JavaScript passes objects by reference, so it's never going to store a clone of itself through assignment.
If you're looking to record modifications made to the object over time, the best way would be to use an array to hold cloned copies of it (or at least the properties that've changed).
To give you the quickest example:
var Product = function(){
};
var product = new Product();
product.history = [];
product.saveState = function(){
var changes = {};
for(var i in this){
/** Prevent infinite self-referencing, and don't store this function itself. */
if(this[i] !== this.history && this[i] !== this.saveState){
changes[i] = this[i];
}
}
this.history.push(changes);
};
Obviously, there're many better ways to achieve this in JavaScript, but they require more explanation. Basically, looping through an object to store its properties is inevitably going to trip up upon the property that they're being assigned to, so a check is needed at some point to prevent self-referencing.
The changes array of an Object.observe() callback contains objects with the following four properties:
name
object
type
oldValue
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/observe#Parameters
Why isn't there a path provided natively? Example:
var ob = {
foo: [
{moo: "bar", val: 5},
{val: 8}
]
}
ob.foo[0].val = 1;
// callback should provide path "foo.0.val" or "foo[0].val"
There's a Node.js module that extends Object.observe() to also include the path: observed.js,
but I worry the performance gain of a native observe() will be lost (if no, could you please explain how it is implemented then?). It might be possible to browserify the module, but can't imagine it will perform well in a synchronous environment and I still wonder why nobody seems to have thought about an additional path property.
Because there is no clear path.
Consider the following:
var movall = {moo: "bar", val: 5};
var ob1 = {a: mooval};
var ob2 = {b: movall};
Now let's say I observe movall. Then I update moo. What is the path? Is it movall.moo, or ob1.a.moo, or ob2.b.moo? If I observe ob1, there is no change reported, since there is no change to any of its properties (the change was internal to one of its properties, which doesn't count).
Objects are independent of their existence nested within other objects. They can be nested within multiple other objects. There is no unique "path" that describes how to get from potentially multiple starting points down to a specific property which may have changed.
Nor does JS know the path by which you reached the property being changed. So in ob.foo[0].val = 1;, JS simply evaluates the chain, arrives at the foo[0] object, changes its val property, and at that point has no idea how it happened to arrive at foo[0]. All it knows is that foo[0] has changed. It changed within ob, but it might also have changed within some other object that happens to have foo[0] as a property.
However, you can possibly achieve what you seem to be trying to by building some machinery on top of the low-level observe/notify mechanism. We shall define a function on an object which sets up observers on its property objects, and so on recursively, and propagates change records back up with properly constructed paths:
function notifySubobjectChanges(object) {
var notifier = Object.getNotifier(object); // get notifier for this object
for (var k in object) { // loop over its properties
var prop = object[k]; // get property value
if (!prop || typeof prop !== 'object') break; // skip over non-objects
Object.observe(prop, function(changes) { // observe the property value
changes.forEach(function(change) { // and for each change
notifier.notify({ // notify parent object
object: change.object, // with a modified changerec
name: change.name, // which is basically the same
type: change.type,
oldValue: change.oldValue,
path: k +
(change.path ? '.' + change.path : '') // but has an addt'l path property
});
});
});
notifySubobjectChanges(prop); // repeat for sub-subproperties
}
}
(Note: the change object is frozen and we cannot add anything to it, so we have to copy it.)
Now
a = { a: { b: {c: 1 } } }; // nested objects
notifySubobjectChanges(a); // set up recursive observers
Object.observe(a, console.log.bind(console)); // log changes to console
a.a.b.c = 99;
>> 0: Object
name: "c"
object: Object
oldValue: 1
path: "a.b" // <=== here is your path!
type: "update"
The above code is not production-quality, use at your own risk.
Here is what I need to do. I have an object that goes
{"MainASubB":"AB","MainBSubC":"BC"...}
Every once in a while I need to take out all attributes that start MainA prior to putting in a new attribute starting MainA. In the example above the object transformations would be
{"MainASubB":"AB","MainBSubC":"BC"...} =>
{"MainBSubC":"BC"...} => //MainASubB has now been taken out
{"MainASubD":"AB","MainBSubC":"BC"...}; A new MainA group attribute, MainASubD has now been added.
I am aware of what Javascript delete can do but on its own I dont think it quite takes me all the way there. I should mention that
At times there may be no MainA group attribute present in the first place.
Provided the code works as intended there can never be more than one MainA group attribute.
Stringifying the object, cleaning out the string as required, then de-stringifying it and then finally putting in the new MainA group attribute is certainly possible but I am wondering if there is another techique, perhaps one reliant on jQuery?, that will get me there faster.
You have to iterate over the properties, compare each name and then delete the property:
for (var prop in obj) {
if (prop.indexOf('MainA') === 0) { // property name starts with 'MainA'
delete obj[prop];
break; // since there can be only one
}
}
I wouldn't use such "hierarchical" property names though. Why not use nested objects and just overwrite the value as you see fit?
For example:
var obj = {
MainA: {
SubA: '...'
},
MainB: {
SubA: '...'
}
};
and then it's just:
obj.MainA = {SubD: '...'};
or just add the "sub" value:
obj.MainA.SubD = '...';
This would be more flexible in the long run.
Here is a generic function :
function removeProperties (obj, prop) {
Object.keys (obj).forEach (
function (p) {
if (typeof prop === 'string' ? p.indexOf (prop) == 0 : prop.test (p))
delete obj[p];
});
return obj;
}
The parameter obj is the object wheich you want to remove properties from. Parameter prop can be a string, 'MainA' for example, in which case any properties with names starting with that string will be removed. If 'prop is a regular expression then any properties whose names match it will be removed.
The removal is done in-place, i.e obj itself is modified, it is also returned as the result of the function.
See it in action here : http://jsfiddle.net/jstoolsmith/EpSxC/