Page Content

Tutorials

What is the Readonly Property in TypeScript?

Readonly Property in TypeScript

TypeScript’s readonly modifier is a type system feature that lets you designate specific attributes on an interface, type, or class as unchangeable once they’ve been initialised. Promoting a functional manner of working and avoiding unanticipated data alteration is the main reason for employing readonly.

Functionality and Usage

A readonly property can be read normally, but if you try to change its value outside of certain initialisation contexts, you’ll get a compile-time error.

For class properties, a readonly member must be initialized either at the point of declaration or within the class constructor. Any attempt to modify the property after the constructor finishes will lead to an error.

If a property in a class only includes a getter but no setter, the TypeScript compiler can automatically infer it as readonly.

TypeScript also offers a utility type, Readonly<T>, which accepts a type T and maps over it, marking all of its properties as readonly. Furthermore, you can mark index signatures as readonly, or use the ReadonlyArray<T> interface, which enforces immutable usage of JavaScript arrays by removing mutation methods like push.

Readonly vs. const

Differentiating between readonly and const is crucial:

  • A variable reference that uses const makes sure the variable can’t be changed to anything else.
  • To ensure that a property cannot be altered by the current context, readonly is applied to it.

Readonly keeps me from changing a property, but if the object is aliased and sent to a function that doesn’t share that readonly guarantee (for type compatibility reasons), that method could still change the underlying value. This is a tiny difference.

Code Example: Readonly Class Property

Here is an example demonstrating the use of readonly on class properties, showing that initialization is permitted in the constructor, but subsequent mutation fails:

class Foo {
    readonly bar = 1;      // OK: Initialized at declaration
    readonly baz: string;

    constructor() {
        this.baz = "hello"; // OK: Initialized in constructor
    }

    mutate() {
        this.bar = 456;     // Error: Cannot assign to 'bar' because it is a read-only property.
    }
}

const instance = new Foo();
// instance.baz = "world"; // Error: Cannot assign to 'baz' because it is a read-only property. 

Static Methods and Properties

TypeScript classes are compatible with members that use the static keyword. One characteristic of static properties and methods is that they are shared by all instances of the class.

Functionality and Access

Instead than using an instance of the class to access and define static members, the class itself does so.

In structural typing, only instance members and methods are compared to determine type compatibility between classes; constructors and static members of a class are not involved.

One phase in TypeScript’s inheritance-based class transpilation process is to copy the base class’s static members to the derived (child) class.

Unlike some other languages, TypeScript classes do not have a designated static constructor. However, you may still achieve static initialisation by explicitly executing an initialisation function on the class after it has been declared. During translation, static attributes are added directly to the class object, whereas instance properties are added to the prototype.

Code Example: Static Property and Access

A common use case for a static property is counting the number of instances created.

class StaticTest {
    static countInstance: number = 0; // static class variable 

    constructor() {
        // Increment static property inside the constructor
        StaticTest.countInstance++;
    }
}

// Create instances
new StaticTest();
new StaticTest();
console.log(StaticTest.countInstance); // Access static property via class name 

Output:

2

This output demonstrates that both instantiations share and increment countInstance.

Abstract Classes

Abstract classes serves as foundational classes that other classes must build upon, imposing certain functionality and structure on their offspring.

Principal Features and Limitations

  1. Cannot be Instantiated: It is not possible to instantiate abstract classes directly with the new keyword. You can only instantiate non-abstract (concrete) subclasses.
  2. Combination of Interface and Class: In a conceptual sense, an abstract class is a hybrid of a conventional class and an interface. Along with declaring abstract members, they can create concrete methods with their own implementation bodies.
  3. Abstract Members: The abstract modifier is applied to abstract properties or methods. There is no implementation body for abstract methods in the abstract base class.
  4. Enforcement of Implementation: Every concrete class that derives from an abstract class is required to implement all of the base class’s abstract methods and attributes. A compile-time error happens if a child class does not implement an inherited abstract member, unless the child class is itself defined as abstract.

Interfaces lack the characteristics that abstract classes have, such as the ability to declare constructors, offer default implementations for members, and specify access modifiers (private, protected, public). Additionally, abstract classes can generate runtime code (a JavaScript class).

Code Example: Abstract Class and Implementation

This example defines an abstract class Machine requiring subclasses to implement moreInfo(), while providing a default summary() method.

abstract class Machine {
    constructor(public manufacturer: string) {
    }

    // An abstract class can define concrete methods (implementation included)
    summary(): string {
        return `${this.manufacturer} makes this machine.`;
    }

    // Require inheriting classes to implement methods (no implementation here) 
    abstract moreInfo(): string;
}

class Car extends Machine {
    constructor(manufacturer: string, public position: number, protected speed: number) {
        super(manufacturer);
    }

    move() {
        this.position += this.speed;
    }

    // Must implement the abstract method from Machine
    moreInfo() {
        return `This is a car located at ${this.position} and going ${this.speed}mph!`;
    }
}

// Cannot instantiate Machine directly: new Machine("Konda") results in an Error
let myCar = new Car("Konda", 10, 70); 
myCar.move(); // position is now 80
console.log(myCar.summary());
console.log(myCar.moreInfo());

Output:

Konda makes this machine.
This is a car located at 80 and going 70mph!

The result verifies the successful implementation and call of the necessary abstract method moreInfo() as well as the inheritance and usage of the concrete method summary().

Index