This content originally appeared on Envato Tuts+ Tutorials and was authored by Monty Shokeen
We have come a long way in learning TypeScript since starting this series. The first tutorial gave you a brief introduction of TypeScript and suggested some IDEs that you can use for writing TypeScript. The second tutorial focused on data types, and the third tutorial discussed the basics of interfaces in TypeScript.
As you might already know, JavaScript now also has native support for classes and object-oriented programming. However, TypeScript allowed developers to use classes in their code for a long time. This code is then compiled to JavaScript that will work across all major browsers. In this tutorial, you will learn about classes in TypeScript. They are similar to their ES6 counterparts but are stricter.
Creating Your First Class
Let's start with the basics. Classes are a fundamental part of object-oriented programming. You use classes to represent any entity which has some properties and functions that may act on given properties. TypeScript gives you full control over the properties and functions that are accessible inside and outside their own containing class. Here is a very basic example of creating a Person
class.
class Person { name: string; constructor(theName: string) { this.name = theName; } introduceSelf() { console.log("Hi, I am " + this.name + "!"); } } let personA = new Person("Sally"); personA.introduceSelf();
The above code creates a very simple class called Person
. This class has a property called name
and a function called introduceSelf
. The class also has a constructor, which is also basically a function. However, constructors are special because they are called every time we create a new instance of our class.
You can also pass parameters to constructors in order to initialize different properties. In our case, we are using the constructor to initialize the name of the person that we are creating using the Person
class. The introduceSelf
function is a method of the Person
class, and we are using it here to print the name of the person to the console. All these properties, methods, and the constructor of a class are collectively called class members.
You should keep in mind that the Person
class does not automatically create a person by itself. It acts more like a blueprint with all the information about the attributes a person should have once created. With that in mind, we created a new person and named her Sally. Calling the method introduceSelf
on this person will print the line "Hi, I am Sally!" to the console.
Private and Public Modifiers
In the previous section, we created a person named Sally. Right now, it is possible to change the name of the person from Sally to Mindy anywhere in our code, as shown in the following example.
class Person { name: string; constructor(theName: string) { this.name = theName; } introduceSelf() { console.log("Hi, I am " + this.name + "!"); } } let personA = new Person("Sally"); // Prints "Hi, I am Sally!" personA.introduceSelf(); personA.name = "Mindy"; // Prints "Hi, I am Mindy!" personA.introduceSelf();
You might have noticed that we were able to use both the name
property and the introduceSelf
method outside the containing class. This is because all the members of a class in TypeScript are public
by default. You can also explicitly specify that a property or method is public by adding the keyword public
before it.
Sometimes, you don't want a property or method to be accessible outside its containing class. This can be achieved by making those members private using the private
keyword. In the above code, we could make the name property private
and prevent it from being changed outside the containing class. After this change, TypeScript will show you an error saying that the name
property is private
and you can only access it inside the Person
class. The screenshot below shows the error in Visual Studio Code.
Inheritance in TypeScript
Inheritance allows you to create more complex classes starting from a base class. For example, we can use the Person
class from the previous section as a base to create a Friend
class that will have all the members of the Person
and add some members of its own. Similarly, you could also add a Family
or Teacher
class.
They will all inherit the methods and properties of the Person
while adding some methods and properties of their own to set them apart. The following example should make it clearer. I have also added the code for the Person
class here so that you can easily compare the code of both the base class and the derived class.
class Person { private name: string; constructor(theName: string) { this.name = theName; } introduceSelf() { console.log("Hi, I am " + this.name + "!"); } } class Friend extends Person { yearsKnown: number; constructor(name: string, yearsKnown: number) { super(name); this.yearsKnown = yearsKnown; } timeKnown() { console.log("We have been friends for " + this.yearsKnown + " years.") } } let friendA = new Friend("Jacob", 6); // Prints: Hi, I am Jacob! friendA.introduceSelf(); // Prints: We have been friends for 6 years. friendA.timeKnown();
As you can see, you have to use the extend
keyword for the Friend
class to inherit all the members of the Person
class. It is important to remember that the constructor of a derived class must always invoke the constructor of the base class with a call to super()
before using this
keyword.
You might have noticed that the constructor of Friend
did not need to have the same number of parameters as the base class. However, the first name parameter was passed to super()
in order to invoke the constructor of the parent, which also accepted one parameter. We did not have to redefine the introduceSelf
function inside the Friend
class because it was inherited from the Person
class.
Using the Protected Modifier
Up to this point, we have only made the members of a class either private
or public
. While making them public allows us to access them from anywhere, making the members private limits them to their own containing class. Sometimes you might want the members of a base class to be accessible inside all the derived classes.
You can use the protected
modifier in such cases to limit the access of a member only to derived classes. You can also use the protected
keyword with the constructor of a base class. This will prevent anyone from creating an instance of that class. However, you will still be able to extend classes based on this base class.
class Person { private name: string; protected age: number; protected constructor(theName: string, theAge: number) { this.name = theName; this.age = theAge; } introduceSelf() { console.log("Hi, I am " + this.name + "!"); } } class Friend extends Person { yearsKnown: number; constructor(name: string, age: number, yearsKnown: number) { super(name, age); this.yearsKnown = yearsKnown; } timeKnown() { console.log("We have been friends for " + this.yearsKnown + " years.") } friendSince() { let firstAge = this.age - this.yearsKnown; console.log(`We have been friends since I was ${firstAge} years old.`) } } let friendA = new Friend("William", 19, 8); // Prints: We have been friends since I was 11 years old. friendA.friendSince();
In the above code, you can see that we made the age
property protected
. This prevents the use of age
outside any classes derived from Person
. We have also used the protected
keyword for the constructor of the Person
class. Declaring the constructor as protected
means that we will no longer be able to directly instantiate the Person
class. The following screenshot shows an error that pops up while trying to instantiate a class with the protected
constructor.
Using Getters and Setters in TypeScript
We can use public getters and setters to access and modify the value of private members of a class in TypeScript. You can also place additional logic inside the methods to do things like validate input values. Here is an example:
class Adult { private _name: string; private _age: number; get name() { return this._name; } set name(newName: string) { if(newName.length > 2) { this._name = newName; } else { throw new Error("Name is too short!"); } } get age() { return this._age; } set age(newAge: number) { if(newAge >= 18) { this._age = newAge; } else { throw new Error("Age must be over 18!"); } } } let person = new Adult(); person.name = "Z"; // Error: Name is too short! person.age = 10; // Error: Age must be over 18! person.name = "Joseph"; person.age = 38; console.log(`${person.name} is ${person.age} years old.`); // Joseph is 38 years old.
In the above code, there are a few things worth noticing. The name of all private properties that we want to get and set are now prefixed with a _
like _name
. The getters and setters use the keywords get
and set
while creating a method with the same name as the property but without the prefix. We also did not need to use parenthesis around name
and age
to either set or get the value of those properties using their respective methods. The correct methods were invoked automatically.
The first two assignments throw an error because we are checking inside the setters that the name is at least three characters long and the age is above or equal to 18. The getters and setters come in handy when we want to include additional checks into our code.
There are a few more things you should keep in mind. Implementing only get
but not set
will automatically make a property readonly
. The type of setter parameter is deduced from the return type of getter if it is not specified explicitly. Starting from TypeScript 4.3, you can have a set accessor that accepts different types than the ones returned by the getter.
Implementing Classes with Interfaces
In our previous tutorial about introduction to Interfaces in TypeScript, we learned that Interfaces are basically used to specify the structure of different objects that we will use in our code. Now, we will learn how we can implement classes based on some previously declared interfaces.
interface IPoint { x: number, y: number, position(x: number, y: number): void } interface IRectangle extends IPoint { width: number, length: number, area(width: number, length: number): number } class Point implements IPoint { x: number; y: number; constructor(x: number, y: number) { this.x = x; this.y = y; } position(): void { console.log(`The point is at (${this.x}, ${this.y}).`); } } class Rectangle implements IRectangle { width: number; length: number; x: number; y: number; constructor(x: number, y: number, width: number, length: number) { this.x = x; this.y = y; this.width = width; this.length = length; } area(): number { return this.width*this.length; } position(): void { console.log(`The rectangle is at (${this.x}, ${this.y}).`); } } let myPoint = new Point(10, 20); let myRect = new Rectangle(0, 0, 20, 30); myPoint.position(); myRect.position(); console.log(myRect.area());
We create an IPoint
interface to store coordinates and then extend it to create an IRectangle
interface like we did in the previous tutorial. One additional change here is that we also added a function declaration to the interface.
The Rectangle
class implements IRectangle
which means that it will have these four properties properties and two methods to output its position and display its area.
Final Thoughts
In this tutorial, I have tried to cover the basics of classes in TypeScript. We began the tutorial by creating a very basic Person
class that printed the name of the person to the console. After that, you learned about the private
keyword, which can be used to prevent the members of a class from being accessed at any arbitrary point in the program.
Finally, you learned how to extend different classes in your code using a base class with inheritance. There is a lot more that you can learn about classes in the official documentation.
This content originally appeared on Envato Tuts+ Tutorials and was authored by Monty Shokeen
Monty Shokeen | Sciencx (2017-09-14T04:48:18+00:00) TypeScript for Beginners, Part 4: Classes. Retrieved from https://www.scien.cx/2017/09/14/typescript-for-beginners-part-4-classes/
Please log in to upload a file.
There are no updates yet.
Click the Upload button above to add an update.