Getters and Setters in TypeScript Classes

Table of Contents

  • Introduction
  • What Are Getters and Setters?
  • Defining Getters and Setters in TypeScript
  • Accessing Getters and Setters
  • Benefits of Using Getters and Setters
  • Use Cases for Getters and Setters
    • Example 1: Encapsulating Private Properties
    • Example 2: Computed Properties with Getters
  • Best Practices for Getters and Setters
  • Conclusion

Introduction

In object-oriented programming (OOP), getters and setters are special methods that provide controlled access to an object’s properties. In TypeScript, these are commonly used to implement encapsulation, allowing you to hide an object’s internal state and control how data is accessed and modified.

This article explains the concept of getters and setters in TypeScript, demonstrates how to define them in classes, and explores some common use cases and best practices.


What Are Getters and Setters?

  • Getters are methods that allow you to retrieve or “get” the value of a property in an object.
  • Setters are methods that allow you to modify or “set” the value of a property in an object.

These methods are useful for controlling access to the internal properties of an object. In many cases, getters and setters are used to apply validation or transformation logic when getting or setting values.

Example Without Getters and Setters

Let’s start with a basic class without getters and setters.

class Person {
name: string;

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

const person = new Person('Alice');
console.log(person.name); // Alice
person.name = 'Bob'; // Directly modifying the property
console.log(person.name); // Bob

In this case, the property name is directly accessed and modified, which may not always be desirable if you need to enforce certain constraints or transformations.


Defining Getters and Setters in TypeScript

In TypeScript, you can define getters and setters using the get and set keywords. These are part of the class definition and act as normal methods but are accessed like properties.

Getters

A getter method is defined using the get keyword. It allows you to define logic for retrieving a property value.

class Person {
private _name: string;

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

get name(): string {
return this._name;
}
}

const person = new Person('Alice');
console.log(person.name); // Alice (accessed like a property)

Setters

A setter method is defined using the set keyword. It allows you to define logic for setting a property value.

class Person {
private _name: string;

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

get name(): string {
return this._name;
}

set name(value: string) {
if (value.length < 3) {
throw new Error('Name must be at least 3 characters long');
}
this._name = value;
}
}

const person = new Person('Alice');
console.log(person.name); // Alice

person.name = 'Bob'; // Setting a new name
console.log(person.name); // Bob

// Throws error: Name must be at least 3 characters long
// person.name = 'Jo';

In this example:

  • The get name() method returns the value of the private _name property.
  • The set name(value) method allows you to modify the _name property with additional logic to ensure the value is valid (e.g., ensuring the name is at least 3 characters long).

Accessing Getters and Setters

Once you’ve defined getters and setters in a class, they behave like regular properties when accessed. However, behind the scenes, they are actually calling the getter and setter methods.

class Person {
private _age: number;

constructor(age: number) {
this._age = age;
}

get age(): number {
return this._age;
}

set age(value: number) {
if (value < 0) {
throw new Error('Age cannot be negative');
}
this._age = value;
}
}

const person = new Person(30);

console.log(person.age); // 30 (getter in action)

person.age = 35; // Setting a new age (setter in action)
console.log(person.age); // 35

// Throws error: Age cannot be negative
// person.age = -5;

In this example, person.age is accessed and modified just like a normal property, but behind the scenes, it triggers the getter and setter methods.


Benefits of Using Getters and Setters

  1. Encapsulation: Getters and setters allow you to hide the internal representation of a property while still providing controlled access to it.
  2. Validation: Setters enable you to validate values before modifying a property. This ensures the property is always in a valid state.
  3. Computed Properties: You can use getters to define computed properties that return a value based on other properties or internal logic.
  4. Lazy Loading: Getters can be used to implement lazy loading of values, where the value is only computed or fetched when it’s needed.
  5. Read-Only Properties: If you only provide a getter and not a setter, the property becomes read-only.

Use Cases for Getters and Setters

Example 1: Encapsulating Private Properties

One of the main reasons for using getters and setters is to encapsulate an object’s internal state. For example, you might want to store a value privately and only allow access to it through specific methods that control how it is modified.

class Product {
private _price: number;

constructor(price: number) {
this._price = price;
}

get price(): number {
return this._price;
}

set price(value: number) {
if (value < 0) {
throw new Error('Price cannot be negative');
}
this._price = value;
}
}

const product = new Product(100);
console.log(product.price); // 100

product.price = 120; // Setter in action
console.log(product.price); // 120

// Throws error: Price cannot be negative
// product.price = -50;

In this case, the price property is protected from invalid values through the setter.

Example 2: Computed Properties with Getters

Getters can also be used for computed properties that calculate their value based on other properties in the object.

class Rectangle {
constructor(private width: number, private height: number) {}

get area(): number {
return this.width * this.height;
}

get perimeter(): number {
return 2 * (this.width + this.height);
}
}

const rectangle = new Rectangle(5, 10);
console.log(rectangle.area); // 50 (computed using getter)
console.log(rectangle.perimeter); // 30 (computed using getter)

In this example, area and perimeter are computed properties accessed through getters, eliminating the need for explicit methods.


Best Practices for Getters and Setters

  1. Encapsulate Internal State: Use getters and setters to protect internal state and ensure data consistency.
  2. Avoid Complex Logic in Getters/Setters: While it’s tempting to put a lot of logic in getters and setters, it’s best to keep them simple. Avoid putting complex calculations or business logic inside them.
  3. Use Descriptive Names: When defining getters and setters, make sure the names clearly reflect the property being accessed or modified.
  4. Use Read-Only Properties When Needed: If a property should only be read and not modified, only define a getter method.

Conclusion

Getters and setters in TypeScript provide a powerful way to encapsulate an object’s internal state and enforce validation, calculation, or transformation logic when accessing or modifying properties. By using these methods, you can ensure better control over your data, improving code quality and maintainability.

By following the best practices mentioned in this article, you can effectively leverage getters and setters to create more robust and maintainable TypeScript classes.