I have an abstract Typescript class with a constructor. This constructor accepts an object and then tries to set properties of the class with the data in the object.
export default class AbstractModel {
constructor(properties: object) {
Object.assign(this, properties);
}
}
Then there are several classes inheriting this AbstractModel class.
For example:
import AbstractModel from "./abstract-model";
export default class Derived extends AbstractModel {
firstname!: string;
lastname!: string;
age: number = 1;
}
My assumption was that when I create a new Derived and pass {age: 10} in the constructor the newly created Derived would have age set to 10. However, age always seems to be the initial value of 1. Other properties without an initial values are being set as expected.
const derived = new Derived({firstname : "John", lastname: "Doe", age: 10});
Output:
Derived {
firstname: 'John',
lastname: 'Doe',
age: 1,
}
Logging this in the constructor of AbstractModel gives me the following output
export default class AbstractModel {
constructor(properties: object) {
Object.assign(this, properties);
console.log(this);
}
}
Output:
Derived {
firstname: 'John',
lastname: 'Doe',
age: 10,
}
Logging this in the constructor of Derived gives me the following output
export default class Derived extends AbstractModel {
firstname!: string;
lastname!: string;
age: number = 1;
constructor(properties: object) {
super(properties);
console.log(this);
}
}
Output:
Derived {
firstname: 'John',
lastname: 'Doe',
age: 1,
}
Can anyone tell me why it is behaving like this?
The initial value you provide for
ageis assigned/defined¹ by your subclass constructor. That assignment/definition is done just after the call tosuperin your subclass constructor (either your explicit constructor if you provide one, or the default generated one if you don't). As a result,agewill be1in your subclass, because the assignment done by the superclass constructor gets overwritten. Here's a simpler example (just using JavaScript so we can run it in Stack Snippets):The generated constructor for
Derivedis:..and the definition/assignment¹ setting
ageto1happens just after thesuper(...args)part of that.I think the minimal-changes way to fix it in the code you've shown is to remove the default value and provide a constructor for
Derived:or
or similar (playground link).
That said, the
AbstractModelconstructor code isn't typesafe, it'll copy over any and all own properties of the object you provide it. You may want to refactor that.¹ Whether
Deriveddoes an assignment (effectively,this.age = 1) or a redefinition (Object.defineProperty(this, "age", {value: 1, /*...*/})) depends on theuseDefineForClassFieldsconfiguration option. It does assignment when the option isn't enabled, and redefinition when it is. Redefinition is the way JavaScript's class fields are standardized, so the configuration option was added to TypeScript to support that newly-standard behavior when class fields were introduced to JavaScript.