Classes in TypeScript

Learn how to use Classes in TypeScript with this detailed tutorial. Includes basic and advanced examples to help you master Classes.

Introduction

Classes in TypeScript are a powerful way to create reusable components. They provide a means of creating objects with properties and methods, and they support inheritance, access modifiers, and much more.

Basic Examples

Here are some basic examples of using Classes in TypeScript:

Defining a Class

A basic example of defining a class in TypeScript.

class Greeter {
    greeting: string;

    constructor(message: string) {
        this.greeting = message;
    }

    greet() {
        return `Hello, ${this.greeting}`;
    }
}

let greeter = new Greeter("world");
console.log(greeter.greet()); // Hello, world

Inheritance

Classes can extend other classes using the extends keyword.

class Animal {
    move(distanceInMeters: number = 0) {
        console.log(`Animal moved ${distanceInMeters}m.`);
    }
}

class Dog extends Animal {
    bark() {
        console.log("Woof! Woof!");
    }
}

const dog = new Dog();
dog.bark(); // Woof! Woof!
dog.move(10); // Animal moved 10m.
dog.bark(); // Woof! Woof!

Public, Private, and Protected Members

TypeScript provides three access modifiers: public, private, and protected.

class Animal {
    private name: string;

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

    public move(distanceInMeters: number) {
        console.log(`${this.name} moved ${distanceInMeters}m.`);
    }
}

class Bird extends Animal {
    protected fly(distanceInMeters: number) {
        console.log(`${this.name} flew ${distanceInMeters}m.`); 
        // Error: Property 'name' is private and only accessible within class 'Animal'.
    }
}

const cat = new Animal("Cat");
cat.move(10); // Cat moved 10m.

Advanced Examples

Here are some advanced examples of using Classes in TypeScript:

Abstract Classes

Abstract classes are base classes from which other classes may be derived. They may not be instantiated directly.

abstract class Department {
    constructor(public name: string) {}

    printName(): void {
        console.log("Department name: " + this.name);
    }

    abstract printMeeting(): void; // Must be implemented in derived classes
}

class AccountingDepartment extends Department {
    constructor() {
        super("Accounting and Auditing"); // constructors in derived classes must call super()
    }

    printMeeting(): void {
        console.log("The Accounting Department meets each Monday at 10am.");
    }

    generateReports(): void {
        console.log("Generating accounting reports...");
    }
}

let department: Department; // ok to create a reference to an abstract type
// department = new Department(); // error: cannot create an instance of an abstract class
department = new AccountingDepartment();
department.printName();
department.printMeeting();
// department.generateReports(); // error: method doesn't exist on declared abstract type

Getters and Setters

You can use getters and setters to control access to the properties of a class.

class Employee {
    private _fullName: string = "";

    get fullName(): string {
        return this._fullName;
    }

    set fullName(newName: string) {
        if (newName && newName.length > 0) {
            this._fullName = newName;
        } else {
            console.log("Error: Please provide a valid name");
        }
    }
}

let employee = new Employee();
employee.fullName = "Bob Smith";
console.log(employee.fullName); // Bob Smith

Static Properties and Methods

Static members are visible on the class itself rather than on the instances.

class Grid {
    static origin = { x: 0, y: 0 };

    calculateDistanceFromOrigin(point: { x: number; y: number }): number {
        let xDist = point.x - Grid.origin.x;
        let yDist = point.y - Grid.origin.y;
        return Math.sqrt(xDist * xDist + yDist * yDist);
    }
}

let grid = new Grid();
console.log(Grid.origin); // { x: 0, y: 0 }

Interfaces and Classes

A class can implement an interface.

interface ClockInterface {
    currentTime: Date;
    setTime(d: Date): void;
}

class Clock implements ClockInterface {
    currentTime: Date = new Date();

    setTime(d: Date) {
        this.currentTime = d;
    }

    constructor(h: number, m: number) {}
}

Mixins

Mixins are a way to implement multiple inheritance.

class Disposable {
    isDisposed: boolean = false;

    dispose() {
        this.isDisposed = true;
    }
}

class Activatable {
    isActive: boolean = false;

    activate() {
        this.isActive = true;
    }

    deactivate() {
        this.isActive = false;
    }
}

class SmartObject implements Disposable, Activatable {
    isDisposed: boolean = false;
    isActive: boolean = false;

    dispose: () => void;
    activate: () => void;
    deactivate: () => void;

    constructor() {
        setInterval(() => console.log(this.isActive + " : " + this.isDisposed), 500);
    }
}

applyMixins(SmartObject, [Disposable, Activatable]);

let smartObj = new SmartObject();
setTimeout(() => smartObj.activate(), 1000);
setTimeout(() => smartObj.deactivate(), 2000);
setTimeout(() => smartObj.dispose(), 3000);

function applyMixins(derivedCtor: any, baseCtors: any[]) {
    baseCtors.forEach(baseCtor => {
        Object.getOwnPropertyNames(baseCtor.prototype).forEach(name => {
            derivedCtor.prototype[name] = baseCtor.prototype[name];
        });
    });
}

Parameter Properties

Parameter properties let you create and initialize a member in one step.

class Octopus {
    readonly numberOfLegs: number = 8;
    constructor(readonly name: string) {}
}

let dad = new Octopus("Man with the 8 strong legs");
console.log(dad.name); // Man with the 8 strong legs

Class Expressions

Class expressions are another way to define classes.

let Greeter = class {
    greeting: string;

    constructor(message: string) {
        this.greeting = message;
    }

    greet() {
        return `Hello, ${this.greeting}`;
    }
}

let greeterInstance = new Greeter("world");
console.log(greeterInstance.greet()); // Hello, world

This Parameter in Methods

You can explicitly indicate that a method does not use the class instance using this.

class MyClass {
    name = "MyClass";
    getName = () => {
        return this.name;
    }
}

let c = new MyClass();
let g = c.getName;
console.log(g()); // MyClass

Conclusion

Classes in TypeScript provide a powerful way to create and manage reusable components. They offer a robust framework for inheritance, encapsulation, and abstraction. By using classes, you can write more organized, maintainable, and scalable code.