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

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.

We’ve renamed our things property to _things , and our name property to _name ; and

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.

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.

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:

While the private modifier is handy, it can’t guarantee privacy, and you shoudn’t rely on it to do so; and

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.

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:

If you declare print with the wrong type in either class, you’ll get a compiler error; and

Each class implements printdifferently .

This last point is important. Different classes will often expose the same abstract behavior — User s 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.

Classes can only inherit from a single parent class. If we need to implement multiple sets of behavior — bothPrintable and Identifiable , for instance — then subclassing doesn’t work.

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:

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 ).