TypeScript - how to copy object A's properties onto new object B that extends A - worked on older ts version now broken

Viewed 33

I have a class B which extends A. I am trying to copy A onto B (since B has all the same properties (and more) as A).

Example:

class TypeA {
    propX: number = 0;
    propY: number = 0;
}

class TypeB extends TypeA {
    propZ: number = 0;
}

let A: TypeA = {propX: 1, propY: 2};
let B: TypeB = new TypeB();

//Here I want to copy A's properties onto B
Object.keys(A).forEach(prop => B[prop] = A[prop]);  //this how we did it on older version of typescript, still works in this example but won't compile in my new project

//now set B's non-shared property
B.propZ = 3;

//desired output {propX: 1, propY: 2, propZ: 3}
console.log(B);

That Ojbect.keys(A) ... line is how we did it on a project with an earlier TS version, but now it won't compile. In fact, in this TS fiddle it will successfully run with desired results. However, there are errors on that line. And in my Angular project, it simply won't compile at all.

How can/should I do this now?

Also, yes I did look at this similar question, but did not find a working solution. The accepted "solution" there does not appear correct to me, despite not understanding it, I tried implementing it to my example with:

let key: keyof TypeA;
for (key in A) {
  A = {
    ...A,
    [key]: B[key]
  }
}

That really doesn't make much sense to me, but I tried it anyway before posting ‍♂️

Thanks for your time.

2 Answers

The old "hey compiler shut up I know what I'm doing".

Object.keys(A).forEach((prop) => { (B as any)[prop] = (A as any)[prop]; });

or

Object.keys(A).forEach((prop) => { (<any>B)[prop] = (<any>A)[prop]; });

Some might say it's bad practice, but I think it's fine in this situation.

You can store your keys in a readonly array and iterate the keys when copying the values. You also use this array to constrain the shape of the base class when defining it:

TS Playground

// Define the common keys in a readonly array:
const keysA = ['propX', 'propY'] as const;
type KeyA = typeof keysA[number];

// Define the base class using an "implements" clause to ensure that
// its shape includes the keys above:
class TypeA implements Record<KeyA, number> {
  propX: number = 0;
  propY: number = 0;
}

class TypeB extends TypeA {
  propZ: number = 0;
}

let A: TypeA = {propX: 1, propY: 2};
// Just a note: A is not an instance of TypeA since you did not construct it:
console.log({'A instanceof TypeA': A instanceof TypeA}); // false

let B: TypeB = new TypeB();

// This is ok because the keys are string literals in the readonly array:
for (const key of keysA) B[key] = A[key];

B.propZ = 3;

console.log({B}); // { propX: 1, propY: 2, propZ: 3 }

Related