2

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?

4
  • const Tipper = new Cat('Tipper', ...sophie)? Commented Jan 6, 2023 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 Commented Jan 6, 2023 at 14:19
  • @evolutionxbox you can only spread an object inside {}. Can't do that when passing arguments Commented Jan 6, 2023 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. Commented Jan 6, 2023 at 14:21

1 Answer 1

4

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)

Sign up to request clarification or add additional context in comments.

1 Comment

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. (I can get a named tuple type with ConstructorParams<typeof Cat>, but that's a type, not a value.)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.