Follow

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use
Contact

Overwriting default values in parent constructor in javascript/typescript

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.

MEDevel.com: Open-source for Healthcare and Education

Collecting and validating open-source software for healthcare, education, enterprise, development, medical imaging, medical records, and digital pathology.

Visit Medevel

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?

>Solution :

The initial value you provide for age is assigned/defined¹ by your subclass constructor. That assignment/definition is done just after the call to super in your subclass constructor (either your explicit constructor if you provide one, or the default generated one if you don’t). As a result, age will be 1 in 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):

class Base {
    constructor() {
        this.age = 42;
    }
}
class Derived extends Base {
    age = 1;
}

console.log(new Derived().age);

The generated constructor for Derived is:

constructor(...args) {
    super(...args);
}

..and the definition/assignment¹ setting age to 1 happens just after the super(...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:

age: number;
constructor(obj: object) {
    super(obj);
    this.age = (obj as any).age ?? 1;
}

or

age!: number;
// ^−−− Note the "definitely assigned" assertion
constructor(obj: object) {
    super({age: 1, ...obj});
}

or similar (playground link).

That said, the AbstractModel constructor 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 Derived does an assignment (effectively, this.age = 1) or a redefinition (Object.defineProperty(this, "age", {value: 1, /*...*/})) depends on the useDefineForClassFields configuration 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.

Add a comment

Leave a Reply

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use

Discover more from Dev solutions

Subscribe now to keep reading and get access to the full archive.

Continue reading