Skip to content
Theory · Classes · lesson 9 of 12

Inheritance

An architect is asked for a house with a garage. He will not draw everything again: he takes the finished house blueprint and adds a garage to it. Everything the old blueprint had, the rooms, the windows, the kitchen, passes to the new one unchanged. If some room has to be redone, the architect marks it on the new blueprint.

In JavaScript this is how extends works: a subclass gets the base class's fields and methods and adds its own. TypeScript keeps this mechanism and checks it: the constructor's arguments, access to fields, and that a redone method really exists in the base class.

TypeScript
class Animal {
    protected nick: string;

    constructor(nick: string) {
        this.nick = nick;
    }
}

class Dog extends Animal {
    bark(): string {
        return `${this.nick}: woof`;
    }
}

console.log(new Dog("Rex").bark()); // Rex: woof

Let's go through the example line by line:

  1. Animal has the field nick and a constructor that fills it.
  2. class Dog extends Animal gets all of that and adds the method bark.
  3. Dog has no constructor of its own, so new Dog("Rex") calls the constructor of Animal.
  4. The program prints Rex: woof.

Since the constructor came from Animal, tsc checks the arguments against it. The call new Dog() without a name does not pass:

TEXT
error TS2554: Expected 1 arguments, but got 0.

protected in a subclass

The field nick is declared protected. The method bark is the subclass's code, so this.nick is available to it. Outside, in the line new Dog("Rex").nick, tsc reports error TS2445: a protected field is visible only to its class and its subclasses. Were the field private, even bark would not see it.

Its own constructor and super

When a subclass has a constructor of its own, it takes on duties:

  • call super(…), the base class's constructor, with its arguments; tsc checks them as it does for new;
  • do it before the first use of this: until the base part of the object is built, nothing may be written to it;
  • not leave the call out entirely: without super(…) you get error TS2377.

override

A subclass may replace a base class method with its own; this is called overriding. The keyword override before a method says it is a replacement:

TypeScript
class Greeter {
    greet(who: string): string {
        return `Hello, ${who}`;
    }
}

class LoudGreeter extends Greeter {
    override greet(who: string): string {
        return super.greet(who).toUpperCase();
    }
}

const g: Greeter = new LoudGreeter();
console.log(g.greet("Anna")); // HELLO, ANNA
  1. override greet replaces the method greet from Greeter. Its parameter and result types must fit the base class's method.
  2. super.greet(who) calls the original method of Greeter, and LoudGreeter turns its result into upper case.
  3. The variable g has the type Greeter, while the object in it is a LoudGreeter.
  4. The program prints HELLO, ANNA.

override protects you from typos. If you write override gret(…), tsc notices that the base class has no such method:

TEXT
error TS4117: This member cannot have an 'override' modifier because it is not declared in the base class 'Greeter'. Did you mean 'greet'?

Without override such a typo would pass silently: the class would get a new method gret, and greet would stay the old one. By default writing override is not required. The --noImplicitOverride flag makes it required: then tsc reports every replacement without override.

A subclass fits the base type

In the example above a LoudGreeter object sits in a variable of type Greeter, and tsc allows it. Here the blueprint picture stops working: an architect may knock out a room of the old blueprint, while a subclass cannot remove anything. Everything Greeter has, LoudGreeter has too, so its object fits wherever a Greeter is expected.

Which method does g.greet("Anna") call? The method of the object itself, that is, LoudGreeter's. The variable's type decides something else: what tsc allows you to call. Through g only the members of Greeter are available, and a new method of the subclass cannot be called through it: that is error TS2339.

Interviewers ask this often: when a subclass object sits in a variable of the base type, which class's method runs? The answer: the subclass's, because the object picks the method, not the variable's type.

Common mistake

In the subclass's constructor its own field was written before super was called:

TypeScript
class Animal {
    protected nick: string;

    constructor(nick: string) {
        this.nick = nick;
    }
}

class Dog extends Animal {
    breed: string;
    constructor(nick: string, breed: string) {
        this.breed = breed; // ✗
        super(nick);
    }
}
TEXT
error TS17009: 'super' must be called before accessing 'this' in the constructor of a derived class.

"super must be called before this is used in a subclass's constructor." JavaScript does not allow it either and throws a ReferenceError at run time. Swap the two lines: first super(nick), then this.breed = breed.

Key points

  • extends passes fields and methods to a subclass, and tsc checks the arguments of new against the constructor the subclass got.
  • A subclass's own constructor calls super(…) before the first this.
  • override marks a method replacement, and tsc checks that the base class has such a method.
  • A subclass object fits the base class's type, and the method of the object itself is called.