Andrei Nemes
Andrei Nemes

Reputation: 3122

TypeScript interfaces and implementation

I was having a play around with TypeScript and interfaces. I have the following piece of code

interface Control {
    name: string;
    onSelect():string;
}

class Button {
    name:string="button";
    onSelect = function() {
        return "hello";
    }
}

var a:Button = new Button();
var b:Control = {"name": "arbitrary", "onSelect": () => { return "ahoy!"; }};

var trigger = function(c:Button) {
    console.log(c.name, "says", c.onSelect());
}

trigger(a);
trigger(b);

Which compiles and runs without complaining. Can anyone please explain why my trigger function accepts b even though it expects to get a type Button and b is of type Control.

Even if Button were to explicitly implement Control, I'm asking for a Button not a Control. For all intended purposes, Button may contain additional members.

Is TypeScript inferring the implementation just because they are structurally the same? Are you allowed to pass an interface where an implementing class is expected? (shouldn't it be the other way around?)

Upvotes: 4

Views: 8979

Answers (2)

Rich Turner
Rich Turner

Reputation: 11014

As described in this TypeScript doc:

One of TypeScript’s core principles is that type checking focuses on the shape that values have. This is sometimes called “duck typing” or “structural typing”.

In "duck typing":

If it looks like a duck and swims like a duck, then in it's likely a duck.

In other words, TypeScript examines the shape (methods and properties) of a supplied object or type. If the supplied object contains all the properties and methods described by an interface, then the object can, in all likelihood, be treated as an object that is compatible with the described interface.

In your example, your interface and class look exactly the same, so TypeScript treats your object as compatible with the described interface.

If you examine the generated JavaScript, note that there is no mention of your interface at all - this is because TypeScript Interfaces are essentially developer-supplied type metadata that help TypeScript validate type compatibility.

Upvotes: 8

John
John

Reputation: 6553

Both your interface and class have the same implementation, so while its expecting a Button you are passing in a compatible object (has name that returns string and onSelect function that returns a string)

If you add another property to Button such as greeting: string it will error on trigger(b) saying that its not valid.

interface Control {
    name: string;
    onSelect:() => string; // this is another way to write function that returns a string
}

class Button implements Control {
    name:string="button";
    onSelect = function() {
        return "hello";
    }
    greeting: string; // adding this will cause trigger(b) to fail
}

Upvotes: 2

Related Questions