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:
- Part 1 - Getting Started with TypeScript
- Part 2 - TypeScript Grammar
- Part 3 - TypeScript Classes and Interfaces
- Part 4 - Modules
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.