TypeScript Classes and Interfaces - Part 3

It’s likely that you’ve used classes and interfaces in statically typed languages to organizing your code into logical units. When you work with JavaScript these constructs do not exist, so we use the excellent replacements like constructor functions and the module pattern. However, you may still miss classes and interfaces (which play together quite nicely) and if so, you may want to take a closer look at TypeScript. Classes are being discussed for the next ECMAScript 6 revision, and as such, TypeScript has taken them under its wing.

Dan Wahlin and I just released a new course for Pluralsight that focuses on providing the fundamentals to write application-scale code using TypeScript.

This post should give you a primer on what’s in the 3rd module of our course (where we cover classes and interfaces in depth). This is part 3 of 4 in an article series where I describe the different areas Dan and I feel are fundamental to learning TypeScript. You can find the other posts here:

Do I Need Classes?

I generally consider 3 main conditions where I consider a class, regardless of the language.

  • Creating multiple new instances
  • Using inheritance
  • Singleton objects

There are others, but let’s look at these 3. For singletons I could just as easily use the Module Pattern, and frankly I generally do that with TypeScript. For creating instances, classes are nice but they are not necessary in JavaScript either.

Let me be clear that classes are pretty awesome, but it’s not like we can’t do these things in JavaScript today using other techniques. The reason TypeScript’s classes are valuable is not because you can’t do them today, it’s because they make it so much easier to do these things. If you want an example, try to write your own class structure that uses inheritance in JavaScript.

If you take anything out of this post, take this: TypeScript makes it easier to write structured code, and part of that is using classes.

Creating a Class

You can create a class and even add fields, properties, constructors, and functions (static, prototype, instance based). The basic syntax for a class is as follows:

// TypeScript
class Car {
    // Property (public by default)
    engine: string;

    // Constructor 
    // (accepts a value so you can initialize engine)
    constructor(engine: string) {
        this.engine = engine;
    }
}  

This code creates a Car class that has a constructor that accepts a single parameter engine that initializes the instance property with the same name. so we can write

var hondaAccord = new Car('V6');

The engine property is defined as being public (public is the default). We could have simply put the public keyword there too. The property could be made private by prefixing the definition with the keyword private. Inside the constructor the engine property is referred to using the this keyword. This code emits this JavaScript:

// JavaScript
var Car = (function () {
    function Car(engine) {
        this.engine = engine;
    }
    return Car;
})();

Functions

You can create a function on an instance member of the class, on the prototype, or as a static function. Often you will want to use the prototype for the functions on classes if you intend to create multiple instances of the object. Why? Because the prototype can save you some memory as it only exists once while creating a function on every instance of the class would create 1 function per instance. Creating a function on the prototype is easy in TypeScript, which is great since you don;t even have to know you are using the prototype.

// TypeScript
class Car {
    engine: string;
    constructor (engine: string) {
        this.engine = engine;
    }
start() {
    return "Started " + this.engine;
}

}

Notice the start function in the TypeScript code. Now look at the emitted JavaScript below, which defines that start function on the prototype.

// JavaScript
var Car = (function () {
    function Car(engine) {
        this.engine = engine;
    }
    Car.prototype.start = function () {
        return "Started " + this.engine;
    };
    return Car;
})();

But if you want to create a function as an instance member, you can do so like this:

// TypeScript
class Car {
    engine: string;
         stop: () => string;
    constructor (engine: string) {
        this.engine = engine;
        this.stop = () => "Stopped " + this.engine;
    }
start() {
    return "Started " + this.engine;
}

}

One way to create a function like this is to do it in the constructor. The emitted JavaScript now shows that the start function is on the prototype and the stop is an instance function. It’s not a great design to mix these, your better off picking one or the other.

// JavaScript
var Car = (function () {
    function Car(engine) {
        var _this = this;
        this.engine = engine;
        this.stop = function () {
            return "Stopped " + _this.engine;
        };
    }
    Car.prototype.start = function () {
        return "Started " + this.engine;
    };
    return Car;
})();

Inheritance

Class inheritance, as you are probably familiar with it, is not is not something you’d want to hand code in JavaScript. This is an area that TypeScript clearly shines brightly. For example, you can define an Auto class and then define a ManlyTruck class that inherits from Auto. This would provide access to all the public members and the constructor to the ManlyTruck class. It can refer to the base class using the super keyword and it can extend the definition by adding its own members.

// TypeScript
class Auto {
    engine: string;
    constructor(engine: string) {
        this.engine = engine;
    }
} 

class ManlyTruck extends Auto {
bigTires: bool;
constructor(engine: string, bigTires: bool) {
super(engine);
this.bigTires = bigTires;
}
}

TypeScript emits JavaScript that helps extend the class definitions, using the __extends variable. This helps take care of some of the heavy lifting on the JavaScript side.

var __extends = this.__extends || function (d, b) {
    function __() { this.constructor = d; }
    __.prototype = b.prototype;
    d.prototype = new __();
};
var Auto = (function () {
    function Auto(engine) {
        this.engine = engine;
    }
    return Auto;
})();
var ManlyTruck = (function (_super) {
    __extends(ManlyTruck, _super);
    function ManlyTruck(engine, bigTires) {
        _super.call(this, engine);
        this.bigTires = bigTires;
    }
    return ManlyTruck;
})(Auto);

Interfaces

One of the coolest parts of TypeScript is how it allows you to define complex type definitions in the form of interfaces. This is helpful when you have a complex type that you want to use in your application such as an object that contains other properties. For example, we could define an interface for a Car class such that every car must have an engine and a color like this.

// TypeScript
interface ICar{
    engine: string;
    color: string;
}

class Car implements ICar {
constructor (public engine: string, public color: string) {
}
}

The Car class adheres to the interface ICar because it implements ICar. You can use interfaces on classes but you can also use them to define regular variables types. Notice the code below defines the toyotaCamry variable to use the type ICar.

// TypeScript 
var toyotaCamry : ICar;

This Car sample also uses the alternate syntax in the constructor to define that not only are the engine and color parameters being passed to the constructor but they are also defining a public member of the Car class. This is a nice shortcut syntax since you don;t have to define the members nor set them in the constructor: it does this for you, as you can see in the emitted JavaScript.

// JavaScript
var Car = (function () {
    function Car(engine, color) {
        this.engine = engine;
        this.color = color;
    }
    return Car;
})();

Structure

I can see developers who are familiar with Object Oriented Programming (OOP) feeling very comfortable with TypeScript’s classes and interfaces. There is a lot of value here in getting running quickly and feeling secure that you have written solid code. There is so much more that classes and interfaces can do too, as Dan and I show in our TypeScript course at Pluralsight.