0

I have a really weird problem, let me just explain:

Obj1: DetailsType = {property1: 123, property2: [{subProp1: 'a', subProp2: 'b'}]}
Obj2: DetailsType = new DetailsType(Obj1)

This is the constructor of DetailsType:

constructor(value: DetailsType = <DetailsType>{}){
    this.property1 = (value.property1 !== undefined) ? value.property1 : null
    this.property2 = (value.property2 !== undefined) ? value.property2 : []
}

Now I run the following code

this.Obj2.property1 = 987
this.Obj2.property2[0].subProp1 = 'z'

At this point, for some reason, the value of Obj1.property2[0].subProp1 is 'z' Even though we changed the value of subProp1 for Obj2! However, Obj1.property1 is still 123

So why does changing property2 which is an array, affect the value on both objects?? How can property1, a number, work correctly, but property2 work so weirdly? It works vice versa, whether I change subProp1 for Obj1 or Obj2. I'm so confused.

Thanks for your help!

Rezzy
  • 111
  • 1
  • 2
  • 10
  • 2
    https://javascript.info/object-copy (I'm sure there is an on-site canonical dupe target for this, but i don't have it in my history/favorites) – ASDFGerte Feb 02 '22 at 13:06
  • 2
    The array references for property2 are the same and the objects contained in that array are also references to same object in each instance. Primitive values do not get assigned as references however , the value is copied directly to the new variable – charlietfl Feb 02 '22 at 13:19
  • How can I changed the constructor so the array references for property2 are passed by value rather than reference? – Rezzy Feb 02 '22 at 13:22
  • `Array.from(arr)` – Ghaith Troudi Feb 02 '22 at 13:27
  • Unfortunately Array.from(arr) doesn't fix the problem – Rezzy Feb 02 '22 at 13:48

2 Answers2

2

It is happening because the value.property2 is an object with many nested references to other objects inside it. You need to deep clone the value.property2 in the constructor:

class DetailsType {
  constructor(value) {
    this.prop1 = (value.prop1 !== undefined) ? value.prop1 : null
    this.prop2 = (value.prop2 !== undefined) ? JSON.parse(JSON.stringify(value.prop2)) : []
  }
}

let obj1 = {
  prop1: 123,
  prop2: [{
    subProp1: 'a',
    subProp2: 'b'
  }]
};
let obj2 = DetailsType = new DetailsType(obj1);

obj2.prop1 = 987
obj2.prop2[0].subProp1 = 'z'

document.body.innerHTML += `obj1:<pre>${JSON.stringify(obj1, undefined, 2)}</pre>`;
document.body.innerHTML += `obj2:<pre>${JSON.stringify(obj2, undefined, 2)}</pre>`;

To find out various ways of deep cloning objects refer:

the Hutt
  • 16,980
  • 2
  • 14
  • 44
1

The current answer (with JSON.stringify) is maybe not the worst, because it will work in this case. But we don't know, how your objects in the array of prop2 will actually look like. Because it is a TypeScript class, it could be, that you also add some methods to it. With JSON.stringify though, all methods/functions would be lost. Only the value-properties are retained, but also not in the right order. The result is just some anonymous object.

Because it is sometimes necessary to clone objects (and not have their properties referenced to the original object), it is pretty useful to add a .clone() function that can be used with every object and array.

This will work by just adding the following code:

(<any>Object.prototype).clone = function() {
  const clone = (this instanceof Array) ? [] : {};
  for (const propertyName in this) {
    if (propertyName == 'clone') continue;
    if (this[propertyName] && typeof this[propertyName] == "object") {
      clone[propertyName] = this[propertyName].clone();
    } else clone[propertyName] = this[propertyName]
  } return clone;
};

With your given code, it will look like this (pretty clean):

class DetailsType {
  public prop1;
  public prop2;
  constructor(value) {
    this.prop1 = value.prop1 !== undefined ? value.prop1 : null;
    this.prop2 = value.prop2 !== undefined ? value.prop2.clone() : [];
  }
}

The .clone() method works both for arrays and objects and will create a deep copy.

Kevin Glier
  • 1,346
  • 2
  • 14
  • 30