1

I have a Typescript class:

class Cat {
  constructor(
    public name:  string,
    public age:   number,
    public color: string
  ) {}
}

const sophie = new Cat('Sophie', 17, 'black')

Now I want to make a copy of the instance, but change one property. If this were a literal object, that would be easy:

const tipper = {...sophie, name: 'Tipper'}

But that doesn't create an instance of the Cat class. To do that I have to laboriously list every property:

const Tipper = new Cat('Tipper', sophie.age, sophie.color)

Is there a cleaner way to copy a class instance, not literal object, while modifying selected properties?

entio
  • 3,816
  • 1
  • 24
  • 39
Sasgorilla
  • 2,403
  • 2
  • 29
  • 56
  • `const Tipper = new Cat('Tipper', ...sophie)`? – evolutionxbox Jan 06 '23 at 14:14
  • One hack would be: `Object.assign(new Cat(), sophie, { name: 'Tipper' })`. This would only work if all those arguments are added as public enumerable properties. A better approach would be to add a static factory method to create new instance based on another instance – adiga Jan 06 '23 at 14:19
  • @evolutionxbox you can only spread an object inside `{}`. Can't do that when passing arguments – adiga Jan 06 '23 at 14:21
  • @Sasgorilla is it correct that you want to clone an instance and change it? I've edited a question, initially i thought you just want to extend a class. – entio Jan 06 '23 at 14:21

1 Answers1

3

You can create a static factory method which returns new instance based on the object sent

class Cat {
  constructor(...) {...}
  
  static fromObject({ name, age, color }) {
    return new Cat(name, age, color)
  }
}

And then send the sophie object with name updated:

const tipper = Cat.fromObject({ ...sophie, name: "Tipper" })

class Cat {
  constructor(name, age, color) {
    this.name = name;
    this.age = age;
    this.color = color
  }
  
  static fromObject({ name, age, color }) {
    return new Cat(name, age, color)
  }
}

const sophie = new Cat('Sophie', 17, 'black')
const tipper = Cat.fromObject({ ...sophie, name: "Tipper" })

console.log(tipper)
console.log(tipper instanceof Cat)

OR

If you can change the signature of the constructor, you can take an object as the argument directly

class Cat {
  constructor({ name, age, color }) {
     ...
  }
}

new Cat({ ...sophie, name: "Tipper" })

class Cat {
  constructor({ name, age, color }) {
    this.name = name;
    this.age = age;
    this.color = color
  }
}

const sophie = new Cat({ name: "Sophie", age: 17, color: "black" })
const tipper = new Cat({ ...sophie, name: "Tipper" })

console.log(tipper)
console.log(tipper instanceof Cat)
adiga
  • 34,372
  • 9
  • 61
  • 83
  • I'm hoping there's a way to automate the `fromObject` approach, but it seems that would require a reliable way to derive an ordered list of the names of the constructor arguments, which [I'm not sure exists](https://stackoverflow.com/questions/68331836/get-arguments-of-constructor-in-js-class). (I can [get a named tuple type with `ConstructorParams`](https://stackoverflow.com/a/73457773/4185992), but that's a type, not a value.) – Sasgorilla Jan 06 '23 at 16:14