4

I am new to typescript and I am stuck in understanding constructor interfaces and the way they are type checked. Here is a snippet from docs:

interface ClockConstructor {
    new (hour: number, minute: number): ClockInterface;
}
interface ClockInterface {
    tick();
}

function createClock(ctor: ClockConstructor, hour: number, minute: number): ClockInterface {
    return new ctor(hour, minute);
}

class DigitalClock implements ClockInterface {
    constructor(h: number, m: number) { }
    tick() {
        console.log("beep beep");
    }
}
class AnalogClock implements ClockInterface {
    constructor(h: number, m: number) { }
    tick() {
        console.log("tick tock");
    }
}

let digital = createClock(DigitalClock, 12, 17);
let analog = createClock(AnalogClock, 7, 32);

Here is what doc says about the above code:

Because createClock’s first parameter is of type ClockConstructor, in createClock(AnalogClock, 7, 32), it checks that AnalogClock has the correct constructor signature.

Now this essentially means that DigitalClock class or AnalogClock classes have the type defined by ClockConstructor interface. How? It's a class and interface is describing a constructor.

Any takers please?

1 Answer 1

4

Let's start with the simple interface in your example:

interface ClockInterface {
    tick();
}

This interface defines that an instance that is of this type contains the tick method, these two implement this interface:

class MyClock implements ClockInterface {
    public tick(): void {
        console.log("tick");
    }
}

let a: ClockInterface = new MyClock();
let b: ClockInterface = {
    tick: () => console.log("tick")
}

It's pretty straight forward, as the class implementation is the same as in other OO languages, the 2nd implementation is not as trivial but should be easy to understand for javascript developers.

This works great! but what happens if I want to get a constructor of a class as an argument for my function?
This won't work:

function constructorClock(ctor: ClockInterface): ClockInterface {
    return new ctor();
}

The argument here is an instance of ClockInterface and not the class (/the constructor function), so to handle such a scenario we can define an interface for the class itself instead of the instances:

interface ClockConstructor {
    new (hour: number, minute: number): ClockInterface;
}

And now we can have this function:

function constructorClock(ctor: ClockConstructor): ClockInterface {
    return new ctor(3, 5);
}

Another issue that these builder interfaces give us is the ability to have definition for the static class members/methods, a good example for this is the ArrayConstructor (which is part of the lib.d.ts):

interface ArrayConstructor {
    new (arrayLength?: number): any[];
    new <T>(arrayLength: number): T[];
    new <T>(...items: T[]): T[];
    (arrayLength?: number): any[];
    <T>(arrayLength: number): T[];
    <T>(...items: T[]): T[];
    isArray(arg: any): arg is Array<any>;
    prototype: Array<any>;
}

(the definition varies depending on if you're using ES5 or ES6 targets, this is the ES5 one).

As you can see, the interface defines different constructor signatures, the prototype and the isArray function, as you use it like this:

Array.isArray([1,2])

If you did not had the ability to have interfaces for the classes themselves (instead of the instances) then you wouldn't be able to use this isArray function, because this is wrong:

let a = [];
a.isArray(3);

The classes DigitalClock and AnalogClock implement the ClockInterface by having the tick method (that is their instances have this method), but they implement the ClockConstructor interface with their constructor function which is used with new and it returns an instance of ClockInterface.

Hopes this helps clarifying it


Edit

The constructor does not return an interface of course, it returns an instance which implements this ClockInterface interface.
Maybe this will make it easier:

class BaseClock {
    protected hour: number;
    protected minute: number;

    constructor(hour: number, minute: number) {
        this.hour = hour;
        this.minute = minute;
    }

    public tick() {
        console.log(`time is: ${ this.hour }:${ this.minute }`);
    }
}

class DigitalClock extends BaseClock {
    constructor(hour: number, minute: number) {
        super(hour, minute);
    }

    tick() {
        console.log("digitial");
        super.tick();
    }
}

class AnalogClock extends BaseClock {
    constructor(hour: number, minute: number) {
        super(hour, minute);
    }

    tick() {
        console.log("analog");
        super.tick();
    }
}

interface ClockConstructor {
    new (hour: number, minute: number): BaseClock;
}

function createClock(ctor: ClockConstructor, hour: number, minute: number): BaseClock {
    return new ctor(hour, minute);
}

Instead of an interface, we're now using classes only, does that make more sense?

The syntax: new (hour: number, minute: number): ClockInterface defines a constructor, this:

function createClock(ctor: ClockConstructor, hour: number, minute: number): ClockInterface {
    return new ctor(hour, minute);
}

createClock(DigitalClock, 12, 17);

Is like:

function createDigitalClock(hour: number, minute: number): ClockInterface {
    return new DigitalClock(hour, minute);
}

createDigitalClock(12, 17);

new ctor(hour, minute); (where ctor is ClockConstructor) is like new DigitalClock(hour, minute) (just more generic).

Sign up to request clarification or add additional context in comments.

4 Comments

Well my question was this: How does interface ClockConstructor represents a class type? Maybe I don't understand the syntax: new (hour: number, minute: number): ClockInterface; How does return type as ClockInterface makes sense?
Check my revised answer
yes base class example makes sense. I am still a bit confused about this: new (hour: number, minute: number): ClockInterface. Should I take it as a syntax to define the interface of a class which implements some constructor?
All typescript classes implement a constructor interface, because all class instances are created like new ClassName(...params?: any[]). So yes, the syntax for a constructor type (a class) is: { new (...params?: any[]): ClassOrInterfaceType }, i.e.: function create(ctor: { new (x: number, y: number): Point }) { ... }

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.