node.jstypescriptabstract

Inheritance of abstract classes with configuration in constructor


I come from the PHP world and am currently implementing my first TypeScript project. Unfortunately many things don't work here that I'm used to in PHP for a long time. One of these things is the following code example:

abstract class Car {

    constructor() {
        this.initCar()
    }

    abstract initCar(): void

    abstract start(): void
}

class SportCar extends Car {

    private engine = ''
    
    initCar(): void {
        this.engine = 'V8'
    }

    start() {
        console.log(`I'm starting the ${this.engine} engine.`)
    }
}

const sportCar = new SportCar()
sportCar.start()

My expectation is that I'm starting the V8 engine. will be output. but unfortunately I'm starting the engine. will be output.

  1. what does TypeScript + JavaScript do with this example?
  2. how do I solve such cases where I want to use the constructor of an abstract class to configure the child objects.

Solution

  • This can be answered by looking at the Javascript output:

    "use strict";
    class Car {
        constructor() {
            this.initCar();
        }
    }
    class SportCar extends Car {
        constructor() {
            super(...arguments);
            this.engine = '';
        }
        initCar() {
            console.log('initCar');
            this.engine = 'V8';
        }
        start() {
            console.log(`I'm starting the ${this.engine} engine.`);
        }
    }
    const sportCar = new SportCar();
    sportCar.start();
    

    As you can see, the this.engine = '' line is executed after the constructor.

    I think this is pretty surprising behavior too. The easiest way to solve it for this case is to not initialize engine as an empty string, because after all... why would you? You're always going to overwrite it.

    class SportCar extends Car {
    
        private engine!: string;
        
        initCar(): void {
            console.log('initCar');
            this.engine = 'V8';
        }
    
        start() {
            console.log(`I'm starting the ${this.engine} engine.`)
        }
    }