Skip to content
Advertisement

Subclassing in TypeScript and using more specific properties or methods

In TypeScript, how can I get a more specific subclass of a more generic class to allow referencing more specific properties and methods of that subclass when parent classes define them further up the inheritance tree?

To explain my question, I think this code sums up what I’m trying to achieve:

class Driver {}

class Formula1Driver extends Driver {
  
  useFormula1Skills () {

    console.log('Go go go!')
  
  }
}

class Car {

  public driver: Driver

  constructor(driver: Driver) {

    this.driver = driver;

  }
}

class Formula1Car extends Car {

  constructor(driver: Formula1Driver) {

    super(driver)

    this.driver.useFormula1Skills()
    // TS Error: Property 'useFormula1Skills' does not exist on type 'Driver'.(2339)

  }
}

Note the type error above.

A Car must have a Driver, but a Formula1Car must have a Formula1Driver and be able to call Formula1Driver-specific properties and methods.

I do not want Driver to define useFormula1Skills for obvious reasons, and figured that by stipulating that a Formula1Driver must be passed to the constructor in Formula1Car, that the type checker would allow my referencing of the subclass-specific method.

Advertisement

Answer

You can narrow down (override) the property in the subclass definition:

class Formula1Car extends Car {

  constructor(public driver: Formula1Driver) {

    super(driver)

    this.driver.useFormula1Skills()
      
  }
}

It wouldn’t work the other way arround.

Note that above notation is equivalent to:

class Formula1Car extends Car {
  public driver: Formula1Driver
  constructor(driver: Formula1Driver) {
    super(driver)
    this.driver = driver;
    ...
  }
}

The reason it works is that Car requires that the driver is Driver and F1Driver indeed is a Driver (note that since Driver does not have any properties, any object can be considered a Driver). When overriding properites, you can safely narrow them down – any kind of Driver can drive a Car, therefore F1Driver is okay to be type of driver in F1Car.

It is a design choice of typescript to be easier to work with but it indeed is supectible to runtime errors like in this case:

const vettel = new Formula1Driver();

const astonMartin = new Formula1Car(vettel);

const f1OnTheStreet: Car = astonMartin;

// UH OH!!!
f1OnTheStreet.driver = new Driver();

// remember that f1OnTheStreet is the same instance of F1
astonMartin.driver.useFormula1Skills();
// astonMartin.driver.useFormula1Skills is not a function 

You cannot make a property that you want to access in the subclass private, but you can make it protected, meaning it cannot be accessed from the outside the class but can be access from a subclass. Note that modifiers must match. If you have a private property in Car, you cannot change (access) it in F1Car. If you have public in Car, you cannot make it private in F1Car.

class Car {
  constructor(protected driver: Driver) {}
}

class F1Car extends Driver {
  constructor(protected driver: F1Driver) {
     super(driver);
  }
}

This still allows Car to implement something like switchDrivers end end up with the same runtime error as above.

To make sure that nobody changes drivers, the property in Car needs to be readonly (and also in the subclasses

class Car {
   constructor(public readonly driver: Driver) {}
}

Just note that these checks are only compile time so anything can happen if you access them without type checking.

User contributions licensed under: CC BY-SA
4 People found this is helpful
Advertisement