Classes

The reason why it’s important to have classes in JavaScript as a first class item is that:

  1. Classes offer a useful structural abstraction
  2. Provides a consistent way for developers to use classes instead of every framework (emberjs,reactjs etc) coming up with their own version.
  3. Object Oriented Developers already understand classes.

Finally JavaScript developers can *have class*. Here we have a basic class called Point:

class Point {
    x: number;
    y: number;
    constructor(x: number, y: number) {
        this.x = x;
        this.y = y;
    }
    add(point: Point) {
        return new Point(this.x + point.x, this.y + point.y);
    }
}

var p1 = new Point(0, 10);
var p2 = new Point(10, 20);
var p3 = p1.add(p2); // {x:10,y:30}

This class generates the following JavaScript on ES5 emit:

var Point = (function () {
    function Point(x, y) {
        this.x = x;
        this.y = y;
    }
    Point.prototype.add = function (point) {
        return new Point(this.x + point.x, this.y + point.y);
    };
    return Point;
})();

This is a fairly idiomatic traditional JavaScript class pattern now as a first class language construct.

class Foo {}; 
var someVar = Foo; //variable space declaretion
var someOtherVar = 123; //Valid

New.target

You can use in TypeScript the new.target meta-property which enables you to determine if a function or constructor was invoked using the new operator. It allows you to detect whether an object was created as a result of a constructor call.

class Parent {
    constructor() {
        console.log(new.target); // Logs the constructor function used to create an instance
    }
}

class Child extends Parent {
    constructor() {
        super();
    }
}

const parentX = new Parent(); // [Function: Parent]
const child = new Child(); // [Function: Child]

Inheritance

Classes in TypeScript (like other languages) support single inheritance using the extends keyword as shown below:

class Point3D extends Point {
    z: number;
    constructor(x: number, y: number, z: number) {
        super(x, y);
        this.z = z;
    }
    add(point: Point3D) {
        var point2D = super.add(point);
        return new Point3D(point2D.x, point2D.y, this.z + point.z);
    }
}

If you have a constructor in your class then you must call the parent constructor from your constructor (TypeScript will point this out to you). This ensures that the stuff that it needs to set on this gets set. Followed by the call to super you can add any additional stuff you want to do in your constructor (here we add another member z).

Note that you override parent member functions easily (here we override add) and still use the functionality of the super class in your members (using super. syntax).

Statics

TypeScript classes support static properties that are shared by all instances of the class. A natural place to put (and access) them is on the class itself and that is what TypeScript does:

class Something {
    static instances = 0;
    constructor() {
        Something.instances++;
    }
}

var s1 = new Something();
var s2 = new Something();
console.log(Something.instances); // 2

You can have static members as well as static functions.

Access Modifiers

TypeScript supports access modifiers public,private and protected which determine the accessibility of a class member as shown below:

accessible onpublicprotectedprivate
classyesyesyes
class childrenyesyesno
class instancesyesnono

If an access modifier is not specified it is implicitly public as that matches the convenient nature of JavaScript 🌹.

Note that at runtime (in the generated JS) these have no significance but will give you compile time errors if you use them incorrectly. An example of each is shown below:

class FooBase {
    public x: number;
    private y: number;
    protected z: number;
}

// EFFECT ON INSTANCES
var foo = new FooBase();
foo.x; // okay
foo.y; // ERROR : private
foo.z; // ERROR : protected

// EFFECT ON CHILD CLASSES
class FooChild extends FooBase {
    constructor() {
      super();
        this.x; // okay
        this.y; // ERROR: private
        this.z; // okay
    }
}

As always these modifiers work for both member properties and member functions.

In TypeScript, constructors can be marked as private or protected, which restricts their accessibility and usage.

Private Constructors: An experimental proposal to the ECMAScript standard introduces the concept of a private field by using the hash (#) symbol before a property name.

// Class 'ClassES6Private' defines a private property '#id' of type number
class ClassES6Private {
  // Property '#id' is a private field and can only be accessed within the class
  #id: number;

  // Constructor initializes the private '#id' field with the passed 'id' argument
  constructor(id: number) {
    this.#id = id;
  }
}

// Instantiate a new object of type 'ClassES6Private' with an 'id' of 10
let es6PrivateClass = new ClassES6Private(10);

// Attempt to change the private '#id' field
es6PrivateClass.#id = 20;

Can be called only within the class itself. Private constructors are often used in scenarios where you want to enforce a singleton pattern or restrict the creation of instances to a factory method within the class

Protected Constructors: Protected constructors are useful when you want to create a base class that should not be instantiated directly but can be extended by subclasses.

class BaseClass {
    protected constructor() {}
}

class DerivedClass extends BaseClass {
    private value: number;

    constructor(value: number) {
        super();
        this.value = value;
    }
}

// Attempting to instantiate the base class directly will result in an error
// const baseObj = new BaseClass(); // Error: Constructor of class 'BaseClass' is protected.

// Create an instance of the derived class
const derivedObj = new DerivedClass(10);

These modifiers are essential for enforcing encapsulation and establishing boundaries for accessing and modifying the internal state of a class.

The private modifier restricts access to the class member only within the containing class.

The protected modifier allows access to the class member within the containing class and its derived classes.

The public modifier provides unrestricted access to the class member, allowing it to be accessed from anywhere.“

Abstract

abstract can be thought of as an access modifier. We present it separately because opposed to the previously mentioned modifiers it can be on a class as well as any member of the class. Having an abstract modifier primarily means that such functionality cannot be directly invoked and a child class must provide the functionality.

  • Can an abstract class have a constructor in TypeScript? Yes, and it can be called by subclasses.
  • Can an abstract class have static methods in TypeScript? Yes, and they can be overridden in subclasses.
  • abstract classes cannot be directly instantiated. Instead the user must create some class that inherits from the abstract class.
abstract class FooCommand {}

class BarCommand extends FooCommand {}

const fooCommand: FooCommand = new FooCommand(); // Cannot create an instance of an abstract class.

const barCommand = new BarCommand(); // You can create an instance of a class that inherits from an abstract class.
  • abstract members cannot be directly accessed and a child class must provide the functionality.
abstract class FooCommand {
  abstract execute(): string;
}

class BarErrorCommand  extends FooCommand {} // 'BarErrorCommand' needs implement abstract member 'execute'.

class BarCommand extends FooCommand {
  execute() {
    return `Command Bar executed`;
  }
}

const barCommand = new BarCommand();

barCommand.execute(); // Command Bar executed

Abstract vs Interface

An abstract class can have methods with implementation but an interface can’t.

Constructor is optional

The class does not need to have a constructor. e.g. the following is perfectly fine.

class Foo {}
var foo = new Foo();

Define using constructor

Having a member in a class and initializing it like below:

class Foo {
    x: number;
    constructor(x:number) {
        this.x = x;
    }
}

is such a common pattern that TypeScript provides a shorthand where you can prefix the member with an access modifier and it is automatically declared on the class and copied from the constructor. So the previous example can be re-written as (notice public x:number):

class Foo {
    constructor(public x:number) {
    }
}

Property initializer

This is a nifty feature supported by TypeScript (from ES7 actually). You can initialize any member of the class outside the class constructor, useful to provide default (notice members = [])

class Foo {
    members = [];  // Initialize directly
    add(x) {
        this.members.push(x);
    }
}

Get & Set

Getters and setters are special methods that allow you to define custom access and modification behavior for class properties. They enable you to encapsulate the internal state of an object and provide additional logic when getting or setting the values of properties. In TypeScript, getters and setters are defined using the get and set keywords respectively. Here’s an example:

class MyClass {
    private _myProperty: string;

    constructor(value: string) {
        this._myProperty = value;
    }
    get myProperty(): string {
        return this._myProperty;
    }
    set myProperty(value: string) {
        this._myProperty = value;
    }
}

Auto-Accessors in Classes

TypeScript version 4.9 adds support for auto-accessors, a forthcoming ECMAScript feature. They resemble class properties but are declared with the “accessor” keyword.

class Animal {
    accessor name: string;

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

Auto-accessors are “de-sugared” into private get and set accessors, operating on an inaccessible property.

class Animal {
    #__name: string;

    get name() {
        return this.#__name;
    }
    set name(value: string) {
        this.#__name = name;
    }

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

Abstract Classes

abstract class Animal {
    protected name: string;

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

    abstract makeSound(): void;
}

class Cat extends Animal {
    makeSound(): void {
        console.log(`${this.name} meows.`);
    }
}

const cat = new Cat('Whiskers');
cat.makeSound(); // Output: Whiskers meows.

With Generics

Classes with generics allow you to define reusable classes which can work with different types.

class Container<T> {
    private item: T;

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

    getItem(): T {
        return this.item;
    }

    setItem(item: T): void {
        this.item = item;
    }
}

const container1 = new Container<number>(42);
console.log(container1.getItem()); //  42

const container2 = new Container<string>('Hello');
container2.setItem('World');
console.log(container2.getItem()); // World