TypeScript - Classes

October 3, 2015

This is the third post in a series that amounts to little more than short notes and code samples to reinforce various aspects of TypeScript. Check out the previous post on TypeScript interfaces.

Using Classes

ECMAScript 5 does not support classes as they are implemented in many popular object-oriented languages. However, a number of techniques have been developed over the years to enable the implementation of class-like code. ECMAScript 6 and TypeScript do have formal support for classes. Classes in TypeScript should feel very familiar to any developer familiar with mainstream languages like C# and Java.

Here is a very simple implementation of a Employee class with two properties.

class Employee {  
    name: string;
    age: number;
}

Classes in TypeScript define both a new type and the shape of an object which means you can declare new variables of that type.

let newHire: Employee = new Employee();  
newHire.name = 'Audrey';  
newHire.age = 30;  
console.log('Welcome, ' + newHire.name);  

Private/Public Modifiers

Members in TypeScript are public by default. However, you can use the private keyword to explicity prevent a member from being accessible outside the class.

class Employee {  
    name: string;
    age: number;
    private height: number;
}

let newHire: Employee = new Employee();  
newHire.height = 65; // compile error  

Constructors

You can define a constructor for your class by simply adding a new function to it named constructor. Each class is only allowed to have a single constructor function. You may not overload it the you might in C# or Java. However, functions in TypeScript can accept optional parameters. Defining a constructor with optional parameters gives you some of the flexibility normally available with overloaded constructors.

Parameters are specified as optional by adding a question mark after the parameter name. In the following example, the employeeName parameter is required, but the employeeAge parameter is optional.

class Employee {  
    name: string;
    age: number;
    private height: number;

    constructor(employeeName: string, employeeAge?: number) {
        this.name = employeeName;

        if(employeeAge) {
            this.age = employeeAge;
        }
    }
}

There is a shorthand syntax that allows you to use the parameters to the constructor function to declare and initialize class members. Use the private or public keywords with a parameter will treat the parameter as either a private or public member of the class which removed the need to declare separate members to store the values in the parameters. Using this technique, the class above could be refactored as follows.

class Employee {  
    private height: number;

    constructor(public name: string, public age?: number) {

        console.log('New employee: ' + this.name + ' (age ' + this.age + ')');

    }
}

Accessors

You can add get/set accessors for public properties if you need greater control over what happens when a particular property is read or written. They are not required.

The getter uses the get keyword followed by the name of the property and an empty parameter list. The setter uses the set keyword followed by the property name and takes exactly one parameter. The property can be referenced on instances of the class exactly as you would other properties that do not have explicit accessors.

The following class implements accessors for a new title property on the Employee class.

class Employee {  
    private _height: number;
    private _title: string;

    constructor(public name: string, public age?: number) {

        console.log('New employee: ' + this.name + ' (age ' + this.age + ')');

    }

    get title(): string {
        return this._title.toUpperCase();
    }

    set title(newTitle: string) {
        this._title = newTitle.toLowerCase();
        console.log('new title stored in class: ' + this._title);
    }
}

let newHire: Employee = new Employee('Audrey', 20);  
newHire.title = 'Software Developer';  
console.log('Title for the new hire: ' + newHire.title);  

Inheritance

A class can inherit from another class in much the same way inheritance works in object-oriented languages. The extends keyword is used to denote that one class inherits from another. If the super class defines a constructor function, but the subclass does not, then new instances of the subclass must be created by passing the parameters expected by the super class. This is demonstrated in the following sample. PickupTruck inherits from MotorVehicle and must be created by passing the numWheels parameter expected by the MotorVehicle constructor.

class MotorVehicle {  
    private _title: string;

    constructor(public numWheels: number) {
        console.log('New vehicle: ' + this.numWheels + ' wheels');
    }
}

class PickupTruck extends MotorVehicle {  
    bedLenth: number;
}

let myTruck = new PickupTruck(4);  
myTruck.bedLenth = 8;  
console.log('Number of wheels: ' + myTruck.numWheels);  
console.log('Bed length: ' + myTruck.bedLenth);  

If the subclass defines its own constructor, then new instances must be created by passing the parameter it expects. However, it is the subclass constructor must call the super class's constructor as shown in the updated example below.

class MotorVehicle {  
    private _title: string;

    constructor(public numWheels: number) {
        console.log('New vehicle: ' + this.numWheels + ' wheels');
    }
}

class PickupTruck extends MotorVehicle {  
    bedLenth: number;

    constructor(public model: string) {
        super(4);
    }
}

let myTruck = new PickupTruck('Silverado');  
myTruck.bedLenth = 8;  
console.log('Model: ' + myTruck.model);  
console.log('Number of wheels: ' + myTruck.numWheels);  
console.log('Bed length: ' + myTruck.bedLenth);  

Methods in the super class may be overridden in the subclass as long as they have the same signature. They may call they super class's implementation of the method, but that is not required as it is with constructor functions. The following example includes a new drive() method in the super class that is overridden in the subclass.

class MotorVehicle {  
    private _title: string;

    constructor(public numWheels: number) {
        console.log('New vehicle: ' + this.numWheels + ' wheels');
    }

    drive(speed: number): void {
        console.log('Driving speed: ' + speed);
    }
}

class PickupTruck extends MotorVehicle {  
    bedLenth: number;

    constructor(public model: string) {
        super(4);
    }

    drive(speed: number): void {
        console.log('About to drive the truck!');
        super.drive(speed);
    }
}

let myTruck = new PickupTruck('Silverado');  
myTruck.drive(50);  

Static Properties

You can't create static classes in TypeScript, but classes can have static properties. They are properties associated with the class itself rather than individual instances of the class. You create staic properties with the static keyword and you read and write them by referencing the property on the class as demonstrated with the numberOfSides property on the Square class in the following example.

class Square {  
    static numberOfSides: number = 4;
    lengthOfSides: number;  
}

let smallSquare = new Square();  
smallSquare.lengthOfSides = 2;  
console.log('Number of sides: ' + Square.numberOfSides);  
console.log('Length of sides: ' + smallSquare.lengthOfSides);  

Abstract Classes

You can use the abstract keyword to define abstract classes in TypeScript. Just like abstract classes in other languages, you cannot directly intantiate an abstract class. It can only be used as the base class for other classes. Unlike interfaces, abstract classes can contain implementation details that are inherited by their subclasses.

Methods within an abstract class may also be marked abstract. Abstract methods do not provide any implementation details and only define the signature of the method which must be implemented in classes that inherit from the abstract class. The following code sample demonstrates all of these features.

abstract class ModelBase {  
    id: string;
    PrintID(): void {
        console.log('My ID: ' + this.id)
    }
    abstract DoStuff(): void;
}

class Department extends ModelBase {  
    name: string;

    DoStuff(): void {
        console.log('busy doing stuff...');
    }
}

//let baseDept: ModelBase = new ModelBase(); // compiler error

let myDept: Department = new Department();  
myDept.id = 'MKTG';  
myDept.PrintID();  
myDept.DoStuff();  

Class Expressions

Class expressions let you define classes without giving them names. You can use them wherever you can use an expression. The following example assigns a class expression to a variable. Since the variable is then a class, I can create a new instance of it using the new keyword. I can then set properties and call methods on it like any other class instance.

let MyClass = class {  
    name: string;
    PrintName(): void {
        console.log('Name: ' + this.name);
    }
}

let newInstance = new MyClass();  
newInstance.name = 'Michelle';  
newInstance.PrintName();  

Class expressions can also be used following the extends keyword to define a base class.

class Athlete extends class { run(): void { console.log('running...')} } {  
    name: string;
}

let baseballPlayer: Athlete = new Athlete();  
baseballPlayer.name = 'Koufax';  
baseballPlayer.run();  

In the next post I'll cover modules.

Author image
About Brice Wilson