Declaring getters/setters in Interfaces and Classes in TS

avatar
Borislav Hadzhiev

Last updated: Jan 24, 2023
6 min

banner

# Table of Contents

  1. Declaring getters in Interfaces in TypeScript
  2. Declaring setters in Interfaces in TypeScript
  3. Using getters and setters in TypeScript classes

# Declaring getters in Interfaces in TypeScript

Use the readonly modifier to declare a getter in an interface.

Consumers of the interface will only be able to read the property, but they won't be able to reassign it.

index.ts
interface Person { readonly name: string; readonly age: number; } const dev1: Person = { name: 'Bobby Hadz', age: 29, }; console.log(dev1.name); // ๐Ÿ‘‰๏ธ "Bobby Hadz" // โ›”๏ธ Error: Cannot assign to 'name' because it is a read-only property. ts(2540) dev1.name = 'Anne'; class Developer implements Person { get name() { return 'Bobby Hadz'; } get age() { return 30; } } const dev2 = new Developer(); console.log(dev2.name); // ๐Ÿ‘‰๏ธ "Bobby Hadz" // โ›”๏ธ Error: Cannot assign to 'name' because it is a read-only property. dev2.name = 'Carl';

The Person interface in the example has 2 readonly properties that serve as a getter.

The dev1 object is of type Person, so its name and age properties can never be reassigned.

In our Developer class, we used an implements clause to indicate that the class is of type Person.

We used getter methods for the name and age properties, so trying to change their values causes an error.

Note that there is no way for us to indicate that the name and age properties in the Person interface will be used specifically as getters.

This is considered an implementation detail, as it shouldn't matter whether we declare a property or a getter, as long as accessing the property returns a value of the correct type.

Technically, the class that implements the interface is free to use a property or a getter.

# Declaring setters in Interfaces in TypeScript

In the same way, there is no way for us to specify that a property is a setter in an interface, but we can still use a setter in our class.

index.ts
interface Person { country: string; } class Developer implements Person { private _country = ''; get country() { return this._country; } set country(c: string) { this._country = c; } } const dev = new Developer(); console.log(dev.country); // ๐Ÿ‘‰๏ธ "" dev.country = 'Germany'; console.log(dev.country); // ๐Ÿ‘‰๏ธ "Germany"

declaring setters in interfaces

The Developer class has a country property that is a getter and setter.

If you only declare a getter for a specific class property, the property is automatically marked as readonly.

You also aren't required to specify the type of the setter parameter. If you don't explicitly specify it, it is inferred from the return type of the getter.

index.ts
interface Person { country: string; } class Developer implements Person { private _country = ''; get country() { return this._country; } // ๐Ÿ‘‡๏ธ (parameter) c: string (It's inferred) // from the return type of the getter set country(c) { this._country = c; } } const dev = new Developer(); console.log(dev.country); // ๐Ÿ‘‰๏ธ "" dev.country = 'Germany'; console.log(dev.country); // ๐Ÿ‘‰๏ธ "Germany"

not required to specify type of setter parameter

In the same way as with getters, the class isn't required to use a getter and setter for the country property.

index.ts
interface Person { country: string; } class Developer implements Person { public country = ''; } const dev = new Developer(); console.log(dev.country); // ๐Ÿ‘‰๏ธ "" dev.country = 'Germany'; console.log(dev.country); // ๐Ÿ‘‰๏ธ "Germany"

We used a basic class property and we still implement the interface correctly.

The reason there is no way for us to specify that a specific property in an interface is a getter or setter is that this is considered an implementation detail.

# Using getters and setters in TypeScript classes

Use the get and set keywords to define getters and setters in TypeScript.

Getters enable us to bind a property to a function that is called when the property is accessed, whereas setters bind a property to a function that is called on attempts to set the property.

index.ts
class Developer { private _language = ''; private _tasks: string[] = []; get language() { return this._language; } set language(value: string) { this._language = value; } get tasks() { return this._tasks; } set tasks(value: string[]) { this._tasks = value; } } const dev = new Developer(); dev.language = 'TypeScript'; console.log(dev.language); // ๐Ÿ‘‰๏ธ "TypeScript" dev.tasks = ['develop', 'test']; dev.tasks.push('ship'); console.log(dev.tasks); // ['develop', 'test', 'ship']

The Developer class has 2 getters and setters.

The get syntax binds an object property to a function, so every time the property is accessed, the function is called.

When we access the language property on an instance of the class, we are calling the language() method.

The set syntax binds an object property to a function and every time there is an attempt to set the property, the function is called.

It's very important to note that even though, we are calling class methods in the background, we use getters and setters as we would use a regular property on an object.

You shouldn't try to call a setter as myInstance.mySetter('TypeScript'), instead you should set the property as myInstance.mySetter = 'TypeScript'.

# Using the private keyword when declaring properties

Notice that we used the private keyword when declaring the _language and _tasks properties in our class.

index.ts
class Developer { private _language = ''; private _tasks: string[] = []; get language() { return this._language; } set language(value: string) { this._language = value; } get tasks() { return this._tasks; } set tasks(value: string[]) { this._tasks = value; } }

Class members with private visibility are only accessible inside the class itself.

This is important because we don't want consumers of our class to be able to access the _language and _tasks properties.

index.ts
class Developer { private _language = ''; private _tasks: string[] = []; get language() { return this._language; } set language(value: string) { this._language = value; } get tasks() { return this._tasks; } set tasks(value: string[]) { this._tasks = value; } } const dev = new Developer(); // โ›”๏ธ Error: Property '_language' is private // and only accessible within class 'Developer'.ts(2341) console.log(dev._language);

Trying to access a private property from outside of the class causes an error, which is exactly what we need in order to make sure that consumers use getters and setters as intended.

The underscore prefix is used because we need a different name for the property in order to avoid infinite loops in our getter and setter methods.

The following implementation causes an infinite loop.

index.ts
class Developer { private language = 'TypeScript'; set language(value: string) { // โ›”๏ธ Error: Maximum call stack exceeded this.language = value; } } const dev = new Developer();

The problem in the code is - we didn't use an underscore to prefix the language property in the class.

So, every time the setter is called, it sets the property and keeps calling itself every time it sets the property.

I've also written a detailed guide on solving the "Maximum stack size exceeded" error in TS.

# Defining only a getter marks the property as read-only

If you only define a getter for a specific property, the property is automatically marked as readonly.

index.ts
class Developer { private _language = 'TypeScript'; get language() { return this._language; } } const dev = new Developer(); console.log(dev.language); // ๐Ÿ‘‰๏ธ "TypeScript" // โ›”๏ธ Cannot assign to 'language' because //it is a read-only property.ts(2540) dev.language = 'TypeScript';

We only assigned a getter for the language property, therefore it cannot be reassigned.

# TypeScript infers the type of the setter parameter

If you don't explicitly type the setter parameter, TypeScript is able to infer it from the return type of the getter.

index.ts
class Developer { private _language = ''; get language() { return this._language; } // ๐Ÿ‘‡๏ธ (parameter) value: string (inferred) // from getter return type set language(value) { this._language = value; } } const dev = new Developer(); dev.language = 'TypeScript'; console.log(dev.language); // ๐Ÿ‘‰๏ธ "TypeScript"

Even though we didn't explicitly type the value parameter in the setter, TypeScript still knows it is of type string because the language getter returns a value of type string.

You are also able to set the value of a property for which you use a getter and setter when instantiating the class.

index.ts
class Developer { private _language = ''; // ๐Ÿ‘‡๏ธ use a constructor method constructor(language: string) { this.language = language; } get language() { return this._language; } set language(value: string) { this._language = value; } } const dev = new Developer('TypeScript'); console.log(dev.language); // ๐Ÿ‘‰๏ธ "TypeScript" dev.language = 'JavaScript'; console.log(dev.language); // ๐Ÿ‘‰๏ธ "JavaScript"

The constructor method is called when the class is instantiated. The method takes the language parameter and initializes its value by using the already-defined setter.

You could have also used the _language property if you don't want to use the setter to initialize the property in the constructor.

index.ts
class Developer { private _language = ''; constructor(language: string) { // ๐Ÿ‘‡๏ธ Not using setter (_language instead of language) this._language = language; } get language() { return this._language; } set language(value: string) { this._language = value; } } const dev = new Developer('TypeScript'); console.log(dev.language); // ๐Ÿ‘‰๏ธ "TypeScript" dev.language = 'JavaScript'; console.log(dev.language); // ๐Ÿ‘‰๏ธ "JavaScript"

The example above doesn't use the setter method to set an initial value for the language property, because we prefixed the property with an underscore.

If the get the error "Maximum stack size exceeded" when using getters and setters, check out the following article.

I wrote a book in which I share everything I know about how to become a better, more efficient programmer.
book cover
You can use the search field on my Home Page to filter through all of my articles.

Copyright ยฉ 2024 Borislav Hadzhiev