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
- Cannot be Instantiated: It is not possible to instantiate abstract classes directly with the
new
keyword. You can only instantiate non-abstract (concrete) subclasses. - 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.
- 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. - 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()
.