TypeScript - Interfaces

September 27, 2015

This is the second post in a series that amounts to little more than short notes and code samples to reinforce various aspects of TypeScript. Check out the first one on TypeScript Basics.

What is an Interface?

Interfaces in TypeScript are similar to interfaces in many object-oriented programming languages. However, in TypeScript, they play the role of defining the "shape" of an object. Objects don't have to explicitly implement interfaces as you would in C# or Java. Instead, interfaces define the expected properties so that the type checker can verify an object with the expected properties is being used.

The following code defines an interface and a function that takes a parameter that adheres to that interface. A new object is then created and passed to the function.

interface Book {  
    title: string;
    author: string;
}

function printTitle(myBook: Book) {  
    console.log(myBook.title);
}

let myBook = {  
    title: "The Things They Carried",
    author: "Tim O'Brien",
    pages: 288,
    yearPublished: 1990
}

printTitle(myBook);  

Note that the myBook object is just an object literal that happens to contain the properties defined on the interface (title and author). It also contains some additional properties not included on the interface. I can pass this object as a parameter to the printTitle function because the function expects a parameter that implements the Book interface and the object contains those required properties. If the object was missing one of the properties defined on the interface, the TypeScript compiler would generate an error.

Duck Typing

Interfaces in TypeScript are a form of duck typing. If something walks, swims, and quacks like a duck, then it must be a duck. Similarly, if a TypeScript object has all of the properties specified on a particular interface, then that object may be used anywhere an object implementing that interface is expected.

In the code above, the myBook object contains several properties, but they include all of the properties defined on the Book interface. Therefore, the myBook object may be passed to the printTitle function since it expects an object that implements that interface.

Optional Properties

You can specify optional properties on interfaces. This is a way of expressing that a property may be present, but allows you to still treat an object as that type if the optional propery is missing. You specify a property as optional by adding a question mark after the property name when defining the interface. The weight property is optional on the following Person interface.

interface Person {  
    name: string;
    age: number;
    weight?: number;
}

Function Types

Interfaces can be used to define the shape of functions just as they can be used to define the shape of objects. Once you define an interface for a function type, you can declare variables of that type and assign functions to the variable as long as the function matches the signature defined in the interface. This is similar to creating a delegate in C#.

The following interface defines a function type that will accept a single number parameter and return a number.

interface SquareItFunc {  
    (theNumber: number) : number;
}

let squareFunc: SquareItFunc;

squareFunc = function(x: number) {  
    return x * x;
}

let numToSquare: number = 5;  
let squaredNumber: number = squareFunc(numToSquare);

console.log(numToSquare + ' squared is ' + squaredNumber);  

Class Types

TypeScript classes can implement an interface just like a class in Java or C# might implement an interface. In the very simple example below, the Vehicle class implements the ICar interface and then a Vehicle instance is passed to a function expecting an object that implement ICar.

interface ICar {  
    horsePower: number;
    drive(speed: number): void;
}

class Vehicle implements ICar {  
    horsePower: number;

    constructor() {
        console.log('inside the vehicle constructor');
    }

    drive(speed: number): void {
        console.log('driving ' + speed + ' miles per hour');
    }
}

function driveFast(car: ICar): void {  
    car.drive(100);
}

let raceCar = new Vehicle();  
driveFast(raceCar);  

Extending Interfaces

Interfaces can be extended using the extends keyword. This is a bit like interface inheritance. In the following example, a new interface is created name IFamilyCar which extends the ICar interface. Objects which implement the IFamilyCar interface will have all of the properties defined on ICar and IFamilyCar.

interface IFamilyCar extends ICar {  
    numPhoneChargers: number;
}

function driveFamily(car: IFamilyCar): void {  
    console.log('horsepower: ' + car.horsePower + ', chargers: ' + car.numPhoneChargers);
}

let sedan: IFamilyCar = <IFamilyCar>{  
    horsePower: 150,
    numPhoneChargers: 4
}

driveFamily(sedan);  

Hybrid Types

Some objects can operate as both a function and an object. Such objects can be implemented as shown below. Objects that implement the Counter interface can be called like a function or used as an object with additional properties and functions on them.

interface Counter {  
    (start: number): string;
    interval: number;
    reset(): void;
}

function getCounter(): Counter {  
    let counter: Counter = <Counter>function(start:number): string {
        console.log('in the counter function');
        return 'done';
    };
    counter.interval = 123;
    counter.reset = function(){ console.log('in the reset function'); };
    return counter;
}

let c: Counter = getCounter();  
c(10);  
c.reset();  
c.interval = 5.0;  

In the next post I'll cover classes in TypeScript.

Author image
About Brice Wilson