Skip to content

Classes with Types

TypeScript adds type safety to JavaScript classes by allowing you to specify types for properties, method parameters, and return values.

Basics

TypeScript classes work just like JavaScript classes but with added type annotations. You can specify types for properties, constructor parameters, and method return values:

ts
class Shape {
  colour: string;

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

  draw() {
    return `A ${this.colour} shape.`;
  }
}

class Square extends Shape {
  size: number;

  constructor(colour: string, size: number) {
    super(colour);
    this.size = size;
  }

  draw() {
    return `A ${this.colour} square size ${this.size}`;
  }
}

Definite Assignment Assertion

Use ! to tell TypeScript: “I promise I will assign a value to this property before it’s used, even if TypeScript can’t see where that happens.” This is helpful when initialization happens outside the constructor, usually through a helper method performing some calculation.

ts
class Square {
  size: number;
  area!: number;

  constructor(colour: string, size: number) {
    super(colour);
    this.size = size;
    this.calculateArea(size)
  }

  calculateSize(s) {
    this.area = s * s;
  }
}

This suppresses TypeScript errors about uninitialized properties.

⚠️ This is different from the non-null assertion (!) used in an expression, discussed later in this introduction.

Abstract Classes and Methods

The abstract keyword allows you to create classes and methods that must be implemented by subclasses. Abstract classes cannot be instantiated directly:

ts
abstract class Shape {
  abstract draw(): string;
}

class Circle extends Shape {
  radius: number;
  
  constructor(radius: number) {
    super();
    this.radius = radius;
  }
  
  draw(): string {
    return `Drawing a circle with radius ${this.radius}`;
  }
}

// const shape = new Shape(); // Error: Cannot create an instance of an abstract class
const circle = new Circle(5);
console.log(circle.draw()); // "Drawing a circle with radius 5"

Access Modifiers

TypeScript provides three access modifiers to control visibility of class members:

  • public: Accessible from anywhere (default)
  • private: Only accessible within the class
  • protected: Accessible within the class and subclasses
ts
class Shape {
  private _colour: string;
  protected _area: number;
  public name: string;

  constructor(colour: string, name: string) {
    this._colour = colour;
    this.name = name;
    this._area = 0;
  }

  // Public method can access private and protected members
  getColour(): string {
    return this._colour;
  }

  // Protected method can be called by subclasses
  protected calculateArea(): number {
    return this._area;
  }
}

class Square extends Shape {
  constructor(colour: string, private size: number) {
    super(colour, "Square");
    this._area = size * size; // Can access protected _area
  }

  getArea(): number {
    return this.calculateArea(); // Can call protected method
  }

  // Cannot access private _colour directly
  // getColourDirect(): string {
  //   return this._colour; // Error: Property '_colour' is private
  // }
}

const square = new Square("red", 5);
console.log(square.name);        // "Square" - public
console.log(square.getColour()); // "red" - via public method
console.log(square.getArea());   // 25 - via public method
// console.log(square._colour);  // Error: Property '_colour' is private
// console.log(square._area);    // Error: Property '_area' is protected

💡 Private Fields: # vs private

TypeScript offers two ways to make fields private:

  • # prefix: JavaScript's native private fields (runtime enforcement)
  • private keyword: TypeScript-only (compile-time enforcement)

With Vite, you can use either approach. The # prefix is more secure since it's enforced at runtime, while private only prevents access during development. For production code, consider using # for true privacy, but the underscore convention (_colour) is still widely used and perfectly valid.

⚠️ Warning: TypeScript allows you to use a shortcut with public constructor parameters (constructor(public colour: string)), but this feature is being phased out in recent TypeScript configurations. The transpiler only removes type annotations and doesn't rewrite JavaScript code, so it's better to explicitly declare properties.

Property Getters and Setters

The get and set keywords let you control how properties are accessed and modified. Adding type annotations to getters and setters is straightforward:

ts
class Circle {
  private _radius: number = 0;  // Private field with underscore prefix

  // Getter and setter for radius
  get radius(): number {
    return this._radius;
  }
  
  set radius(value: number) {
    this._radius = value;
  }

  // Computed property getter
  get area(): number {
    return Math.PI * this._radius * this._radius;
  }
}

const circle = new Circle();
circle.radius = 5;        // Uses setter
console.log(circle.radius); // Uses getter: 5
console.log(circle.area);   // Uses getter: ~78.54

👉 The convention is to use an underscore prefix for internal fields that have public getters and/or setters.

External Resources