TypeScript supports using getters and setters in classes. This enables you to create properties on a class that can be retrieved or assigned dynamically. Let's take a look at a quick example:

class Person {
    public firstName: string;
    public lastName: string;

    constructor(firstName: string, lastName: string) {
        this.firstName = firstName;
        this.lastName = lastName;
    }

    get fullName(): string {
        return `${this.firstName} ${this.lastName}`
    }
}

const tom = new Person("Tomy", "Smith");

console.log(tom.fullName);

In the snippet above, we've created a class that has two properties: firstName and lastName. We've also created a getter function, get fullName(): string, that returns the first and last names as a single string (this is a very basic example that naively assumes everyone has a single first and last name: you should be mindful of the many, many different ways names are formatted and represented around the world).

You can see that, rather than calling fullName() on the instance of the class, we can access it as though it is a property.

We can add a setter, using the set keyword:

class Person {
// ...
    set fullName(name: string) {
        const [first, last, ...rest] = name.split(' ');
        if (rest.length > 0) {
            throw new Error("Name can only have two parts: a first name and a last name.");
        }


        this.firstName = first;
        this.lastName = last;
    }
}

tom.fullName = "Thomas Smith";

Here, we split the passed in name (again, naively assuming that the passed in name is a first name, a space, and then a last name), and assign each part to the firstName and lastName properties. If the passed in name has more than two parts, we throw an error.

As you can see, this allows us to add additional functionality to assignment.

Use Cases

Why would you want to use getters and setters in your code? A few quick examples:

  • You want to set values with side effects, for example, updating the value of an instance might include computing the value, or updating other values alongside it.
  • Retrieval with side effects. For example, retrieving a value from a database or similar, or updating a cache with the new value.
  • You may wish to validate the kind of values that are passed to a setter. For example, only allowing numbers that are greater than a certain amount.
  • You may wish to abstract the internal representation of a value. For example, you could have a rectangle class that has a width property and a height property. You could create getter methods that allow you to return an area or a perimeter measurement using rect.area or rect.perimeter.
  • Backwards compatibility. You could create a getter for a property that used to exist on a class, that simply returns the new way of accessing the property (it could also log a deprecation warning or similar).
  • Fallback values. For example, return a property if it exists on the class, otherwise return a default value.

Gotchas

Accessors with only a get method are automatically inferred to be readonly. This means your code fail to compile if you attempt to assign a value to that property.

When interacting with the classes API, it is unclear if the property is a getter or setter (for example, you don't know that rect.area is a getter, you might assume that it is simply retrieving a property from the instance). Using a function is much better (i.e., rect.getArea()), as it is clear that work is being done to return the value.

Many people don't like accessor methods, for many, many reasons. For more on the pros and cons of the approach, check out Accessors are Evil.