Typing Class Properties in TypeScript: A Developer’s Guide

Updated: January 7, 2024 By: Guest Contributor Post a comment

Introduction

TypeScript, a superset of JavaScript, offers the beguiling art of static typing, granting your code a new vestige of clarity and predictability. This developer guide jests not as it marches you through the gentle sloping hills of typing class properties—a righteous endeavour for any soul daring to harness the power of types in their TypeScript odysseys.

Basic Typing of Class Properties

To begin our journey, pray consider the simple declaration of a class in TypeScript. One must declare a type for each property as a courtesy to the compiler:

class Cat {
    name: string;
    age: number;

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

Here, our feline creation has a name of a string persuasion and an age counted in numbers, anchoring our certainty in its representation in the digital ether.

Advanced Property Types

Let us ascend to the peaked interest of more complex types, such as interfaces and unions:

interface IWhiskers {
    lengthInCentimeters: number;
}

class PersianCat extends Cat {
    whiskers: IWhiskers;

    constructor(name: string, age: number, whiskers: IWhiskers) {
        super(name, age);
        this.whiskers = whiskers;
    }
}

The Persian Cat now brandishes whiskers of a distinct interface, illustrating how one might encapsulate property types into a distinct stratum of abstraction.

Generics and Class Properties

Moving deeper into the TypeScript forest, we find generics—mighty tools allowing our classes to handle a variety of types:

class TreasureChest {
    contents: T;

    constructor(contents: T) {
        this.contents = contents;
    }
}

let chestWithGold = new TreasureChest('Gold Pieces');
let chestWithJewels = new TreasureChest<{'gems': string[], 'value': number}>({'gems': ['Ruby', 'Diamond'], 'value': 5000});

Here, our TreasureChest class can contain any manner of riches—a polymorphic vessel for the baubles and trinkets of code but a type-bound covenant nonetheless.

Property Modifiers: Public, Private, and Protected

Posthaste, let us divulge into property modifiers, which delineate the boundary of accessibility:

class Secret {
    public knownToAll: string;
    private whisperedInShadows: string;
    protected toldInConfidence: string;

    constructor(public truth: string, private lie: string, protected rumor: string) {
        this.knownToAll = truth;
        this.whisperedInShadows = lie;
        this.toldInConfidence = rumor;
    }
}

Observe! The public facing truths, the privately coveted lies, and the protected rumors spreading through the class hierarchy, each with its own standing in the eyes of TypeScript’s guardians.

Readonly Properties

Readonly properties refuse mutation after their initial inscription, steadfast in their resolve:

class Manuscript {
    readonly name: string;

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

const book = new Manuscript('Adventures of TypeScript');
// book.name = 'A New Title'; // This line doth protest for it cannot be!

Attempt to alter the name of our manuscript and find yourself at loggerheads with the compiler’s uncompromising rigidity.

Index Signatures

For the cases when properties are as numerous as the stars, an index signature provides a manifest:

class StarChart {
    [index: string]: 'planet' | 'star' | 'constellation';

    constructor() {
        this['Sirius'] = 'star';
        this['Andromeda'] = 'constellation';
    }
}

A StarChart with a dynamism in properties, yet each celestial body must declare itself within the prescribed types.

Conclusion

Having embarked upon this expedition through the verdant lands of TypeScript’s type systems, we find ourselves seasoned wanderers in the art of typing class properties. Our codes’ architecture stands more noble and steadfast with these edifices of types adorning them, and mayhap our digital adventurers find smoother travels because of them.