‘readonly’ Class Properties in TypeScript

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

Introduction

In the vibrant tapestry of modern software development, TypeScript has emerged as the artisan’s tool for crafting robust and maintainable code. Among TypeScript’s utilitarian features, the ‘readonly’ modifier provides a safeguard, protecting class properties from the tumultuous changes of runtime operations.

Understanding ‘readonly’

Before we sail the seas of code, let us understand what the ‘readonly’ modifier does in TypeScript. It ensures that a class property is immutable after its initial assignment – a steadfast constant amid the mutable vars and lets.

class Example {
  public readonly name: string;
  constructor(name: string) {
    this.name = name;
  }
}
let example = new Example('Immutable Name');
example.name = 'Attempted Change'; // Compiler error!

Delving Deeper

As you grow accustomed to TypeScript’s syntax, it’s akin to navigating a river – it becomes natural to dodge the snares and pitfalls of compilation errors. The ‘readonly’ modifier not only works with primitive types but with object types as well.

class Basket {
  public readonly fruits: string[];
  constructor(fruits: string[]) {
    this.fruits = fruits;
  }
}
const basket = new Basket(['Apple', 'Banana']);
basket.fruits.push('Cherry'); // Allowed!
basket.fruits = ['Strawberry']; // Compiler error!

Combining with Constructors

Adding flair to our crafting, TypeScript allows combining property declaration with constructors – reducing boilerplate for a more elegant code chisel.

class Novel {
  constructor(public readonly author: string, public readonly year: number) { }
}
const novel = new Novel('Mark Twain', 1885);
novel.author = 'Ernest Hemingway'; // Compiler error!

‘readonly’ in Interfaces and Type Aliases

As our code expedition progresses, we find that ‘readonly’ has its place not only in classes but also in defining the shape of data across interfaces and type aliases – a flexible tool for multiple uses.

interface ReadOnlyPoint {
  readonly x: number;
  readonly y: number;
}
let point: ReadOnlyPoint = { x: 10, y: 20 };
point.x = 15; // Compiler error!

Advanced Patterns

Adept practitioners can leverage ‘readonly’ to wield patterns such as Immutable Classes and Readonly Utility Types, ensuring the purity of data handed ‘twixt complex operations.

class ImmutableCircle {
  public readonly radius: number;
  constructor(radius: number) {
    this.radius = radius;
    Object.freeze(this);
  }
}

type ReadonlyCircle = Readonly;

‘readonly’ vs ‘const’

Understanding the nuanced dance between ‘readonly’ and ‘const’ is akin to knowing when to use a feather or a hammer. While ‘const’ defines constants at the scope level, ‘readonly’ is tethered to properties of objects – each with their own symphonic purpose.

const TITLE = 'Author';
// TITLE = 'Editor'; // Error, 'const' can't be reassigned

class Manuscript {
  public readonly title = TITLE;
}
let manuscript = new Manuscript();
manuscript.title = 'Article'; // Compiler error, 'readonly' property can't be reassigned

Conclusion

Armed with the power of ‘readonly’, we emerge from the thicket of mutation with our data integrity unsullied. As our journey through TypeScript continues, let us wield this feature with the skill and precision of a seasoned craftsman, ensuring our code remains as steadfast and unyielding as the prose of Mark Twain himself.