From JavaScript to TypeScript, Pt. IIA: Using Classes, Interfaces, & Mixins

Dig deeper into TypeScript with classes, interfaces, and mixins.

One of TypeScript's main selling points is that it allows developers to use something like classes and interfaces to write a fundamentally prototype-based language.

I say something like, because TypeScript cannot, and does not, add true class-oriented capabilities to the language, which can make for some surprising gotchas. Still, its remarkably clean syntax does a good job of cleaning up the visual cacophany of working with prototypes.

After this article, you'll be able to:

  • Use classes to easily create similar objects;
  • Use interfaces to describe and enforce the shape and behavior of different classes; and
  • Combine classes and interfaces to create robust, self-documentining components.

I assume you're using Node to run the samples; I'm running v6.0.0.

I've uploaded all of the code for this article to my repository on GitHub. If you don't want to type, clone it down, and run:

git checkout Part_2A-Using_Classes_and_Interfaces

If you're on Windows, you should be able to just run tsc. You'll get a frightening bundle of errors, but you can safely ignore them.

If you're on Mac or *nix, you'll still run tsc, but you can also pipe the errors to nowhere if you'd like:

# Compile all TS files, and silence irrelevant errors.
tsc --target ES5 > /dev/null 2>&1

Either way, you should end up with a folder called built, which contains the compiled JavaScript.

Using Classes

The syntax for TypeScript's classes is almost identical to that of JavaScript's native classes, which we'll take as our starting point.

"use strict";

// ListComponent maintains a list of things that a user can
//   add to and delete from. This is a pure JavaScript class.
class ListComponent {

    constructor () {
        this.things = []; 
    }

}

// A ListItem is a wrapper around the items in our list.
//   This is also pure JavaScript, for now.
class ListItem {

    constructor (name) {
        this.name = name;
    }

}

These are perfectly valid JavaScript classes, but ut you'll get errors if you run them through the TypeScript transpiler:

Property does not exist errors

This is one of the few places where the TypeScript compiler will reject raw JavaScript. Let's find out what's wrong, and fix it.

Class Properties

In JavaScript, classes can only contain method definitions. Adding properties is a syntax error.

In TypeScript, you must add instance properties if you assign to them using this. Otherwise, your code won't compile.

// list_classes.ts

"use strict";

class ListComponent {

    // Adding a property is as simpleas adding its name and type
    //   at the top of the class.
    things : Array<ListItem>; // Or -- things : ListItem[];

    constructor () {
        this.things = []; 
    }

}

class ListItem {

    name : string;

    constructor (name) {
        this.name = name;
    }

}

You'll need to add a property for every instance variable you plan to access with this.

Create a new ListItem, and print its name:

const item = new ListItem('Thing to Do');
console.log(item.name); // 'Thing to Do'

. . . And that does away with the first of our errors.

Access Modifiers

We could also have declared our instance variables with the public keyword:

// list_classes_public.ts

"use strict";

class ListComponent {

    public things : Array<ListItem>; // Or -- things : ListItem[];

    constructor () {
        this.things = []; 
    }

}

class ListItem {

    public name : string;

    constructor (name) {
        this.name = name;
    }

}

The public keyword is called an access modifier, and tells the compiler how much of the code should be able to use the property it prefixes.

TypeScript provides two access modifiers:

  1. public. You can read or write public properties from anywhere. These behave identically to object properties in vanilla JavaScript.
  2. private. You can only read or write a private property from inside of the class that "owns" the property.

In other words, you can use private properties to implement methods inside of your class, but you can't touch them from outside.

Private properties help improve consistency and code safety, because the only place you can use them is from within the class. Outsiders can't muck with your data from elsewhere.

Let's see what happens if we declare our things and name properties private.

// list_classes_private.ts

"use strict";

// ListComponent maintains a list of things that a user can
//   add to and delete from. This is a pure JavaScript class.
class ListComponent {

  private things : Array<ListItem>;

    constructor () {
        this.things = []; 
    }

}

// A ListItem is a wrapper around the items in our list.
//   This is also pure JavaScript, for now.
class ListItem {

  private name : string;

    constructor (name) {
        this.name = name;
    }

}

const item = new ListItem('Thing to Do');
console.log(item.name); // Property 'name' is private

The classes are still valid, but you'll notice that the last line causes a compiler error. TypeScript reports: Property 'name' is private and only accessible within the class 'ListItem'.

The most idiomatic way to fix this would be to provide a getter.

"use strict";

class ListComponent {

  // We have to rename the private property so we can name
  //   our "get" method "things". 
  private _things : Array<ListItem>;

    constructor () {
        this._things = []; 
    }

    // Adds a "getter", which returns the private property.
    get things () : Array<ListItem> { return this._things; }

}

class ListItem {

  private _name : string;

    constructor (name) {
        this._name = name;
    }

    get name () : string { return this._name; }

}

const item = new ListItem('Thing to Do');
console.log(item.name); // 'Thing to Do'

If you're not familiar with get methods, check the documentation. Conceptually, they're just functions that JavaScript calls when you access a property with the same name as the get method.

There are two things to notice.

  1. We've renamed our things property to _things, and our name property to _name; and
  2. We've added methods called get things and get name.

We've renamed our properties from thing and name to _thing and _name so we can name our get methods sensibly. There's nothing special about the underscore prefix -- you could have renamed the private properties list_of_things and silicon_unicorn, if you'd wanted -- but using an underscore-prefixed identifier for private members and exposing them via an unprefixed getter is a convenient convention.

Renaming the properties lets us preserve our public interface: The rest of our code doesn't even know we've started using getters under the hood.

If you're wondering why we'd go through all this trouble in the first place, try setting the value of item.name:

// list_item_private_getter.ts

item.name = 'Thing Done';
console.log(item.name); // TypeError: Cannot set property name of #<ListItem> which only has a getter

You'll notice that the TypeScript compiles just fine, but that JavaScript throws at runtime when you try to change the name property.

This is the main advantage to exposing private properties via getters: It prevents them from being set by clients.

You can also define a set method, which allows you to change the name of item as we just attempted. This sometimes makes sense. If you're going to do it that way, though, you can usually get away with avoiding getters/setters altogether, and just declare your properties public.

As a general rule, declaring variables private and exposing them via a getter, but providing no setter, is a good pattern. If you need a setter, provide one. But don't do so unless you have a very good reason: You should generally keep your objects as close to immutable as possible.

Hiding Implementations with Private Methods

TypeScript also allows you to declare private methods.

Let's say we want to validate a user before s/he can add items to a list, but we don't want the validation method available outside the class. Easy: We'd declare it private, which will produce compile-time warnings if we try to access it directly.

// list_classes_private_methods.ts

"use strict";

class ListComponent {

  private _things : Array<ListItem>;

    constructor () {
        this._things = []; 
    }

    get things () : Array<ListItem> { return this._things; }

    get length () : number { return this._things.length; }

    add (item : ListItem, password : string) : boolean {
      if (this.validate(password)) {
        this._things.push(item);
        return true;
      } else
        return false
    }

    private validate (password : string) : boolean {
      if (password === '12345')
        return true
    else
      return false
    }

}

class ListItem {

  private _name : string;

    constructor (name) {
        this._name = name;
    }

    get name () : string { return this._name; }

}

const PASS = '12345'; 

const item = new ListItem('Thing to Do');
const list_one = new ListComponent();
const list_two = new ListComponent();

console.log(list_one.add(item, PASS)); // true
console.log(list_one.length); // 1

console.log(list_two.add(item, 'WRONG password')); // false
console.log(list_two.length); // 0

// console.log(list_one.validate('12345')); // Compiler error

If you try to run list_one.validate('12345'), you'll get a compiler error.

Subclasses

TypeScript also lets you use subclasses, just as does vanilla JavaScript:

"use strict"; 

class CompletedListItem extends ListItem {

    completed : boolean;

    constructor (name : string) {
        super(name);
        this.completed = true;
    }

}

There isn't much more to say about subclasses in TypeScript. For details as to how it all works under the hood, check out my articles on JavaScript's classes; and for some thoughts on designing with subclasses, read the article accompanying this one, on [Designing with Classes & Interfaces]().

Property Shorthand

Finally, TypeScript offers a shorthand for creating and assigning properties.

"use strict";

class Person {

    constructor (public first_name : string, public last_name : string) { }

}

This code is equivalent to:

"use strict";

class Person {

    public first_name : string;
    public last_name : string;

    constructor (first_name : string, last_name : last_name) { }

}

Using this shorthand, we can rewrite our ListComponent like this:

// list_classes_shorthand.ts

"use strict";

class ListComponent {

    constructor (private _things : ListItem[]) { }

    get things () : ListItem[] { return this._things; }

}

class ListItem {

    constructor (private _name) { }

    get name () : string { return this._name; }

}

const item = new ListItem('Thing to Do');
console.log(item.name); // Property 'name' is private

item.name = 'Fish';
console.log(item.name); // 

A Word of Caution

TypeScript's private access modifier is handy, but it is just syntactical sugar. Its compile-time checks against your code do not prevent other code, or even your code, from accessing private properties at runtime.

As an example, while list_one.validate('12345') generates a compiler error, list_one['validate']('12345') does not. That'll actually work just fine.

When you use bracket notaton, JavaScript first evaluates the expression inside of the brackets, and then attempts to access the property on the object with the name of the result. In contrast, TypeScript can analyze direct property access, as in list_one.validate, without the intermediate step of evaluating an expression.

It's just a guess, but I reckon the transpiler doesn't guard against such evaluated property accesses because it would be unreliable: In general, the value of an expression is constrained by its runtime context, which can't be fully determined at compile-time.

The takeaway is twofold:

  1. While the private modifier is handy, it can't guarantee privacy, and you shoudn't rely on it to do so; and
  2. TypeScript's OOP sugar is just sugar. You can still add and delete methods, see and access private properties, and do other such blasphemy at runtime, if you're clever enough.

If you absolutely need private data in your classes, you may be better off using closures, or using one of the several techniques Dr Rauschmayer discusses in Exploring ES6.

Using Interfaces

Classes collect a set of properties and methods into a blueprint you to create objects that differ in state, but otherwise share the same capabilities and shape.

Interfaces allow you to collect a set of properties and behaviors into a blueprint that classes can promise they'll implement, but do not define those properties, nor provide definitions for the methods.

TypeScript uses interfaces for two things: To guarantee that objects created from an implementing class will have a certain shape, or that they'll expose a certain API -- or list of public methods. Each implementing class gets to decide how it will implement that API.

That's all a little abstract, so let's frame it with an example.

Enforcing Shape

The shape of an object is the set of data that it contains. To expect an object to have a certain shape is to expect it to hold certain data. We expect a User object to have a name and an email, for example. These two properties are part of the User object's shape.

An interface is a tool for enforcing a particular shape. If a class implements an interface, it must contain the instance variables and methods listed in the interface.

Creating an interface is much the same as creating a class, except you use the interface keyword instead of class. Otherwise, you list properties and their types identically:

// interfaces.ts
"use strict";

interface User {
    name : string;
    email : string;
}

Any object that implements the User interface must have a name and email property, or the TypeScript compiler will scream at us with an "Interface not implemented properly" error.

Using an interface is like creating a subclass, but you use implements in place of extends.

// interfaces.ts

class RegisteredUser implements User {

  // Shorthand
  constructor (public name : string, public email : string) { }

}

We can also specify certain properties as optional. If we want to add a avatar property but acknowledge that users don't have to have a picture, you mark the property with a question mark.

// interfaces_optional.ts

"use strict";

interface User {
    name : string;
    email : string;
    avatar? : Object;
}

class RegisteredUser implements User {

  constructor (public name : string, public email : string) { }

}

class ImageUser implements User {

  constructor (public name : string,
               public email : string,
               public avatar : Object) { }

}

Method Signatures

Interfaces also allow you to specify methods that a class must implement, while allowing the classes themselves decide on the implementation details.

Including method signatures in an interface is as simple as adding a function signature to the interface.

// interfaces_methods.ts

"use strict";

interface User {
    // PROPERTIES
    name : string;
    email : string;
    avatar? : Object;

  // API
  print () : void;
}

class RegisteredUser implements User {

  constructor (public name : string, public email : string) { }

  print () : void {
    console.log(`Name: ${this.name} | Email: ${this.email} | No avatar.`);
  }

}

class ImageUser implements User {

  constructor (public name : string,
               public email : string,
               public avatar : Object) { }

  print () : void {
    console.log(`Name: ${this.name} | Email: ${this.email} | Has avatar.`);
  }

}

const user = new RegisteredUser('Peleke', 'resil.design@gmail.com');
user.print();

Three things to notice.

  1. If you omit print in either class, you'll get a compiler error;
  2. If you declare print with the wrong type in either class, you'll get a compiler error; and
  3. Each class implements print differently.

This last point is important. Different classes will often expose the same abstract behavior -- Users of all types should be able to print themselves for instance -- but, in general, they might want to do things differently.

Interfaces describe such behavior in the abstract, but allow their implementing classes to define that behavior as they see fit.

Using Classes to Create Mixins

Occasionally, we want classes to expose some API that would be best expressed in terms of an interface -- say, Printable, or Identifiable -- but which they'll all implement in the same way.

One possibility is to create subclasses, but this approach fails on two major fronts.

  1. Classes can only inherit from a single parent class. If we need to implement multiple sets of behavior -- both Printable and Identifiable, for instance -- then subclassing doesn't work.
  2. If the objects that need to implement the interfaces aren't closely related, subclassing is the wrong abstraction. To have a Book and Person both inherit from an Identifiable base class because they both have an ID makes no sense from the standpoint of building a class hierarchy.

The correct abstraction in this case is the interface. But writing the same implementation in each class that implements it is suboptimal. On

one hand, it's more typing. And typing sucks. More seriously, writing the same code in several places is brittle, and if you ever need to change what you intend to be a common implementation, you'll have to rewrite the code in every class that implements the interface.

What we'd want is a way to create an interface that allows us to include implementations. Languages like Groovy solve the problem directly with traits -- in effect, interfaces that can host default method implementations.

JavaScript doesn't directly support traits,but has made use of the mixin pattern for quite some time to achieve the same ends. TypeScript provides a little sugar to create mixins by using classes with the implements keyword.

Partial Classes

Creating a class to use as a mixin is almost identical to creating an interface. There are two differences:

  1. You use the class keyword, instead of interface; and
  2. You include method definitions.
// mixins.ts

"use strict";

class Printable {

    text : string;

    print () : void {
        console.log(this.text);
    }

}

class Identifiable {

    name : string;
    id : number;

    identify () : void {
        console.log(`My name is ${this.name}, and my ID is ${this.id}`);
    }
}

class Book implements Printable, Identifiable { // LINE A

  name : string;
  id : number;

  text : string;

    constructor (name : string, id : number, text : string) {
        this.name = name;
        this.id = id;

        this.text = text;
    }

    // DUMMY methods to satisfy the compiler. If we don't have these, TS
    //   will complain with "Interface not implemented properly".
    // Printable 
    print () : void { }

    // Identifiable
    identify () : void { }

}
applyMixins(Book, [Printable, Identifiable]);

const bad_novel = new Book('A Buncha Nonsense', 2190, 'This book/Is awful. FIN');
bad_novel.print();

// NECESSARY EVIL :: 
//  From TS docs :: http://www.typescriptlang.org/docs/handbook/mixins.html
function applyMixins(derivedCtor: any, baseCtors: any[]) {
    baseCtors.forEach(baseCtor => {
        Object.getOwnPropertyNames(baseCtor.prototype).forEach(name => {
            derivedCtor.prototype[name] = baseCtor.prototype[name];
        });
    });
}

Notice that these "mixin classes" do not contain constructors. That means you can't instantiate them. Instead, you'll use them purely to collect method definitions and property signatures.

Note Line A. The reason we can get away with something as weird as implementing a class is because TypeScript treats classes that you implement as interfaces. In other words, it checks that the implementing class contains the same method signatures and instance variables as the "class" you're implementing, but ignores method implementations. We'll see how to get them back in a minute.

Once you've defined your mixin classes, you create your extending class using implements. And, just as you do when implementing a normal interface, you provide an implementation for the methods that it exposes.

The only difference is that, here, we implement a no-op, dummy implementation. This is for two reasons. If we don't do this, the compiler will complain that our class doesn't implement its interfaces properly.

Of course, we don't want the no-op. We want the method definition inside the partial class. To apply this, we have to use a helper function provided in the TypeScript documentation: applyMixins. It replaces your dummy implementations with the real ones in your partial classes. You'll have to use applyMixins for every mixin class you create.

If you decide to use mixins, I recommend collecting all the applyMixins calls into their own module, or at least into their own chunk of the program. That way you can keep track of where the magic happens.

Frankly, I find all this to be more trouble than it's worth. But you'll see this pattern in the wild, whether you use it or not, so it's important to know that it's available.

Conclusion

In this article, you've seen how to:

  • Build classes with access modifiers;
  • Use interfaces to define the "shape" of a class's data;
  • Use interfaces for modular development of a class's API; and
  • Create mixins using partial classes as interfaces.

As you can imagine, TypeScript's classes, interfaces, and mixins are powerful tools for defining complex behaviors and hierarchies. If you'd like to take a deeper dive into the details of how classes work under the hood, read up on "Class" & Prototypes.

In the companion to this article, Part IIB, we'll take a look at some terminology and desgn principles from the world of OOP; and after that, we'll dive into TypeScript's Types & Generics.

As always, feel free to leave questions in the comments, or shoot them to me on Twitter (@PelekeS).

Peleke Sengstacke

Peleke Sengstacke is a web and Android developer with a soft spot for functional programming.

He likes linguistics, Haskell, and powerlifting. He dislikes mosquitoes, cramped bus rides , and merge conflicts.

Catch him on Twitter (@PelekeS), or sign up for his email list on what to learn and where to learn it (http://www.tinyletter.com/PelekeS). It's wicked educational.