Borislav Hadzhiev
Tue Mar 22 2022·3 min read
Photo by Thomas Bjornstad
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.
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.
You shouldn't try to call a setter as myInstance.mySetter('TypeScript')
,
instead you should set the property as myInstance.mySetter = 'TypeScript'
.
Notice that we used the
private
keyword when declaring the _language
and _tasks
properties in our class.
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 of 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.
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 following implementation causes an infinite loop.
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.
If you only define a getter for a specific property, the property is
automatically marked as readonly
.
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.
If you don't explicitly type the setter parameter, TypeScript is able to infer it from the return type of the getter.
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.
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.
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 does not uses the setter method to set an initial value for
the language
property, because we prefixed the property with an underscore.