11

I have a common typescript class with some functions.

When I do this.selected = Object.assign({} as AssignedTestType, newTestType);

the selected instance does not own that functions which are in the type AssignedTestType.

How can I make a 'deep' copy with object.assign? or any alternative method?

Pascal
  • 12,265
  • 25
  • 103
  • 195
  • 2
    Can't you just pass a `new AssignedTestType` instance as the first argument? – Pointy Dec 21 '16 at 21:44
  • 1
    Lodash `cloneDeep`? – putvande Dec 21 '16 at 21:48
  • It can't copy what's not there. Type assertion affects type checking only, it does not add any actual properties to `{}` - `{} as AssignedTestType` is still an empty object at runtime. You have to provide something that implements all the methods you want to start with. – artem Dec 21 '16 at 21:50
  • No that would be too easy ;-) I have an editable test types table where each tr-tag has a reset/cancel button. Not making a copy and just passing the instance would break the reset/clear button. – Pascal Dec 21 '16 at 21:52
  • @artem the newTestType instance has the implementation of both functions. – Pascal Dec 21 '16 at 21:53
  • Can you provide a reproducible sample of code – Asad Saeeduddin Dec 21 '16 at 21:56
  • 1
    It's iteration through prototypes and doing `assign`. It can also be `$.clone` or `_.cloneDeep`. It makes sense to explain why deep clone is required here. As it was already said, some code is desirable. – Estus Flask Dec 21 '16 at 21:58
  • 1
    @Pascal Maybe these functions are defined in the class of newTestType, that is, in its prototype? [The Object.assign() method only copies enumerable and own properties from a source object to a target object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign). Try `Object.create(newTestType) as AssignedTestType` then. – artem Dec 21 '16 at 22:01
  • @artem that seemed to work but now my button reset feature is broken and I must investigate... – Pascal Dec 21 '16 at 22:23

4 Answers4

11

object.assign does not copy functions

That's untrue

let x = {a: y => y * 2}
let z = Object.assign({}, x)
console.log(z.a(5)); // 10

the selected instance does not own that functions which are in the type AssignedTestType.

Now this part is true. Object.assign will only do a shallow copy and will only enumerate own props

Mulan
  • 129,518
  • 31
  • 228
  • 259
  • 4
    Although the answer does not help me, I mark it as solution as it is theoretically correct. Just my code which I can not put on the table here behaves bogus... – Pascal Dec 21 '16 at 23:05
7

If the properties you're looking to copy are in the prototype of newTestType, they will not be copied, since Object.assign only copies an object instance's own properties.

The alternatives are to either instantiate whatever constructor you used to create newTestType, and assign the resulting instance to this.selected, or to create an empty object and set its prototype to newTestType. In either case, Object.assign is the wrong tool to use here.

Asad Saeeduddin
  • 46,193
  • 6
  • 90
  • 139
  • Its not about properties. Functions() do not get copied! – Pascal Dec 21 '16 at 22:15
  • @Pascal There is no difference between "functions" and properties. Functions in JS are first class objects, you simply assign them to a property of an object to create methods. – Asad Saeeduddin Dec 21 '16 at 22:31
  • 1
    As I mentioned in my answer, the properties that are not being copied (which you say contain functions), are probably on the prototype of the object, and hence will not ignored by `Object.assign`. – Asad Saeeduddin Dec 21 '16 at 22:32
  • The properties/functions I look for are directly put in the implementation of the newTestType instance of type AssignedTestType. Thus you mean they should get copied? – Pascal Dec 21 '16 at 22:54
  • 1
    @Pascal You can't "implement" an instance, you can only instantiate one. Please show your actual code, because otherwise it is impossible to decipher what you are doing. As naomik has demonstrated in his answer, `Object.assign` works just fine with own properties that are functions. – Asad Saeeduddin Dec 21 '16 at 22:58
  • I have not said that I implement an instance. The instance is the implementation during runtime ;-) I can not put the code here as its too much. it would take days then to comment each line that you understand each feature that not each of your advise would break any of those... – Pascal Dec 21 '16 at 23:03
  • 1
    @Pascal You said, "are directly put in the implementation of the newTestType instance". As I said earlier, it makes no sense to speak of the implementation of an instance because instances are not something one "implements". It would be more useful for you to simply provide a reproducible snippet of code instead of fencing with people trying to help you. – Asad Saeeduddin Dec 21 '16 at 23:05
1

How much effort you spend on cloning an object will depend on what you want to do.

First port of call should be .clone or .cloneDeep.

Also, based on the discussion you might be talking about copying references to functions on the prototype. In that case the solution might be as simple as:

class MyClass { foo() {} }
class MyOtherClass extends MyClass {}
console.log(new MyOtherClass().__proto__.foo); // foo from MyClass

If you want to see a hacky clone function, this is a (untested) start (but they can get complex quite fast depending on your requirements):

function clone(o) {
    if(!isObject(o)) {
        throw 'o must be a a non-function object';
    }    
    return (function inner(a, b = {}) {
        Object.keys(a).forEach(k => {
          isObject(a[k])? b[k] = inner(a[k]) : b[k] = a[k];
        });    
        return b;
    }(o));
}

function isObject(o) {
    return o !== null && typeof o === 'object'
}

var a = {
    foo: 'foo',
    bar: () => { console.log('bar'); },
    bat: {
        baz: () => { console.log('baz'); }
    }
};

console.log(clone(a));
Ben Aston
  • 53,718
  • 65
  • 205
  • 331
0

Fast clone complex object:

Code

public static deepClone<T>(obj: any) {
if ( obj === null || obj === undefined) {
  return obj;
} else if ( Array.isArray(obj)) {
  const array: T[] = [];
  obj.forEach( item => array.push( this.deepClone<typeof item>(item) ));
  return array as T[];
} else {
  const c = Object.assign({} as T, obj);
  const fields: string[] = Object.getOwnPropertyNames(obj);
  fields.forEach( f => {
    const field = obj[f];
    if ( typeof field === 'object' ) {
      c[f] = this.deepClone<typeof field>(field);
    }
  });
  return Object.assign({}, obj);
}

}

Example:

const x = {myProp: 'befor change value', c: {a: 1 , b: 2}, d: [1, 2, 3], func: () => {console.log('befor change function'); }};
const y = Object.assign(x);
const z = Utils.deepClone<any>(x);
console.log(x, y, z);
x.c.a = 12;
x.myProp = 'after change value';
x.func = () => console.log('after change function');
console.log(x, y, z);