Override private methods in TypeScript

First things first: a word of caution – do this only if you absolutely have to. TypeScript has these checks for a reason, and probably in 99% of the cases you should respect and abide these restrictions. But if your scenario falls within the 1% where you must in a subclass override a private method of a superclass – this post shows a way to do this.

Let’s say we have a class that defines a generic starship, and what happens to it when it’s hit by a weapon:

type Hit = 'phaser' | 'antimatter spread' | 'photon torpedo'
 
class Starship {
    private totalDamage: number;
 
    constructor(private shipName: string) {
        this.totalDamage = 0;
    }
 
    private addDamage(damage: number) {
        this.totalDamage += damage;
    }
 
    public isHitBy(hit: Hit) {
        console.log(`${this.shipName} is hit by ${hit}!`)
 
        switch(hit) {
            case 'phaser':
                this.addDamage(100);
                break;
            case 'antimatter spread':
                this.addDamage(300);
                break;
            case 'photon torpedo':
                this.addDamage(500);
                break
        }
    }
}

You can create your own class of starship based on this generic class, then build a ship of the new class, and simulate it being hit by a weapon:

class Galaxy extends Starship {
    constructor(name: string) {
        super(name);
    }
}
 
const enterprise = new Galaxy('Enterprise');
enterprise.isHitBy('photon torpedo');

Output:

"Enterprise is hit by photon torpedo!"

This is fine, but as you’ve seen the base class has a secret feature to calculate damage inflicted by the weapon. It is secret, because it’s defined by a private method, and is not exposed to derived classes. But, like Kirk in Kobayashi Maru, you want to break the rules, and have this forbidden knowledge.

If you attempt to add method override in a regular way:

class Galaxy extends Starship {
    constructor(name: string) {
        super(name);
    }
  
    addDamage(damage: number) {
        super.addDamage(damage);
        console.log(`The damage is: ${damage}`)
    }
}

You will be greeted with error message

Property 'addDamage' is private in type 'Starship' but not in type 'Galaxy'.

If you try to change your version of the method to private – TypeScript will complain again:

Types have separate declarations of a private property 'addDamage'.

In order to bypass TypeScript restrictions you can use square bracket notation instead of the dot:

class Galaxy extends Starship {
    constructor(name: string) {
        super(name);
 
        this['addDamage'] = (damage: number) => {
            super['addDamage'](damage);
            console.log(`The damage is: ${damage}`)
        }
    }
}

Now let’s try instantiate the subclass, and try hit it again:

const enterprise = new Galaxy('Enterprise');
enterprise.isHitBy('photon torpedo');

Now the output includes the info from our rule-breaking override:

"Enterprise is hit by photon torpedo!"
"The damage is: 500"

Once again, not to sound like a broken record, but use this approach only if you have to (e.g. if you’re subclassing a class from a library you have no control over, but absolutely need the access to its inner workings).

Leave a Reply

Your email address will not be published. Required fields are marked *