TypeScript Interfaces Tutorial

In this TypeScript tutorial we learn about interfaces that have no member implementation, forcing developers to follow a specific design and allowing us to build loosely coupled applications.

We cover defining and implementing one or more interfaces, as well as how to implement the members of an interface.

Here's a table of contents of what you'll learn in this lesson:
(click on a link to skip to its section)

Let's jump right in.

What is an interface

An interface is like a class whose members aren’t implemented.

If you remember from our tutorial on composition, we spoke about favouring a has-a relationship over an is-a relationship. To recap quickly:

  • Inheritance can be abused, which may lead to a large, fragile hierarchy of classes.
  • Inheritance makes our classes tightly coupled and dependent on each other. We want them to be as loosely coupled as possible.

Interfaces allow us to have easy has-a relationships between classes. Instead of defining a class for each relationship, we define interfaces.

Even though an interface has nothing to do with inheritance, it does allow for polymorphic behavior in a similar way.

How to define an interface

Interfaces are define the same way as classes, except we use the interface keyword instead of the class keyword.

Interface members aren’t implemented. We don’t initialize properties with values, or add code blocks to methods, although they can contain parameters.

Syntax:
interface IInterfaceName {

  property:type;
  method(parameter:type);
}

A popular convention is to prefix the interface name with the capital letter I, to know at a glance that it’s an interface.

Example:
interface IFlyable {

  // properties
  maxSpeed:number;
  // methods
  fly(speed);
}

How to implement an interface

Interfaces aren’t inherited, but rather implemented. We implement an interface with the implements keyword.

Syntax:
interface IInterfaceName {

}

class ClassName implements IInterfaceName {

}
Example:
// Interface
interface IFlyable {

  fly(speed);
}

// Implementations
class Sparrow implements IFlyable {

	fly(speed) {
		console.log("Sparrow flying at a speed of " + speed);
	}

}

class Plane implements IFlyable {

	fly(speed) {
		console.log("Plane flying at a speed of " + speed);
	}

}

// Usage
var sparrow1 = new Sparrow();
sparrow1.fly(10);

var plane1 = new Plane();
plane1.fly(100);

Both ‘Sparrow’ and ‘Plane’ classes implement the ‘IFlyable’ interface and both have to implement their own versions of the fly() method.

It’s similar to what we did in the lesson on composition but with interfaces, we force other developers to follow our design. If a method exists in the interface, a developer has to implement a version of it.

If a class does not implement their own version of the methods in the interface, the compiler will raise an error.

Example:
interface IFlyable {

  fly(speed);
}

class Sparrow implements IFlyable {}

In the example above, we don’t have the fly() method in our ‘Sparrow’ class, so the compiler raises an error.

Output:
main.ts:6:7 - error TS2420: Class 'Sparrow' incorrectly implements interface 'IFlyable'.
  Property 'fly' is missing in type 'Sparrow' but required in type 'IFlyable'.

6 class Sparrow implements IFlyable {}
        ~~~~~~~

  main.ts:3:3
    3   fly(speed);
        ~~~~~~~~~~~
    'fly' is declared here.

How to implement multiple interfaces

Another benefit of interfaces is that we can implement more than one.

Please note that interfaces aren’t used for multiple inheritance, it’s just a benefit we get when using an interface.

To implement multiple interfaces, we separate them with a comma.

Syntax:
interface IInterfaceName1 {

}

interface IInterfaceName2 {

}

class ClassName implements IInterfaceName1, IInterfaceName2 {

}
Example:
// Interface
interface IFlyable {

  fly(speed);
}
interface IHoppable {

  hop(speed);
}

// Implementation
class Sparrow implements IFlyable, IHoppable {

	fly(speed) {
		console.log("Sparrow flying at a speed of " + speed);
	}
  hop(speed) {
    console.log("Sparrow hopping at " + speed + " hops per minute");
  }
}

// Usage
var sparrow1 = new Sparrow();
sparrow1.fly(10);
sparrow1.hop(87);

In the example above, we create another interface called ‘IHoppable’. Because a sparrow can both fly and hop around, it can implement both interfaces.

If we decided to add a ‘Bunny’ class, all we need to do is implement the ‘IHoppable’ interface. Nothing breaks or needs to be changed because it’s all loosely coupled.

Summary: Points to remember

  • Interfaces allow for a loosely coupled has-a relationship between classes, similar to composition.
  • Interfaces force a developer to provide their own implementation.
  • Classes can implement multiple interfaces.