Check out my books on Amazon at https://www.amazon.com/John-Au-Yeung/e/B08FT5NT62
Subscribe to my email list now at http://jauyeung.net/subscribe/
Classes in TypeScript, as in JavaScript, are a special syntax for its prototypical inheritance model that’s a comparable inheritance in class-based object oriented languages. Classes are just special functions added to ES6 that are meant to mimic the class
keyword from these other languages.
In JavaScript, we can have class
declarations and class
expressions because they’re just functions. So like all other functions, there are function declarations and function expressions. This is the same with TypeScript. Classes serve as templates to create new objects. TypeScript extends the syntax of classes of JavaScript and then adds its own twist to it. In this piece, we’ll look at static properties, abstract classes, and constructor functions.
Static Properties
With TypeScript, we can designate members as instance variables, which don’t have the keyword static
before them, and static members, which have the keyword static
keyword before them.
Static members can be accessed without having the class instantiated. Of course, this is given to the access modifiers that are designated for the member. So public static members can be accessed directly from outside the class. Private static members can only be used within the class, and protected members can be accessed by a class the member is defined in and also by subclasses of that class.
The static
keyword can be used by both fields and methods. For example, we can use it like in the following code:
class Person {
static numPeople = 0;
constructor(name: string) {
Person.numPeople++;
}
static getNumPeople() {
return this.numPeople;
}
}
const john = new Person('John');
const jane = new Person('Jane');
console.log(Person.numPeople);
console.log(Person.getNumPeople());
In the code above, every time we instantiate the Person
class, we increase the static property numPeople
by one. Since static properties are shared by all instances of the class and don’t belong to any one instance, we can access numPeople
directly by using Person.numPeople
.
Likewise, we have a static method, getNumPeople
, we can call directly without instantiating the Person
class. Therefore, when we get the numPeople
by using Person.numPeople
and call the Person.getNumPeople()
, then the value 2 is returned for both.
Since the members are static, the values is part of a class and not part of any instance, so even if the instances are destroyed the values will be kept. This is different from instance variables, which are accessed from the this
inside the class and the variable with the instance of the class outside the class.
Abstract Classes
TypeScript has abstract classes, which are classes that have partial implementation of a class and in which other classes can be derived from. They can’t be instantiated directly.
Unlike interfaces, abstract classes can have implementation details for their members. To declare an abstract class, we can use the abstract
keyword. We can also use the abstract
keyword for methods to declare abstract methods, which are implemented by classes that derive from an abstract class.
Abstract methods don’t contain implementations of the method. It’s up to the subclasses that derive from the abstract class to implement the method listed. They may also, optionally, include access modifiers.
We can use abstract classes and methods as demonstrated in the following code:
abstract class Person {
name: string;
age: number;
constructor(name: string, age: number) {
this.name = name;
this.age = age;
} abstract getName(): string;
abstract getAge(): number;
}
class Employee extends Person{
constructor(name: string, age: number) {
super(name, age);
}
getName() {
return this.name;
}
getAge() {
return this.age;
}
}
let employee = new Employee('Jane', 20);
console.log(employee.getName());
console.log(employee.getAge());
In the code above, we have the abstract Person
class, which has the abstract methods getName
and getAge
.
As we can see, the abstract methods only have signatures in them. The actual implementation of the methods are in the Employee
class, which extends the Person
class.
We have the actual implementation of the getName
and getAge
methods in the Employee
class.
TypeScript checks the method signature and the return type of the class, so we must be implementing the abstract methods as it’s outlined in the abstract class. This means that in the example above, the getName
method must take no parameters and must return a string.
Likewise, the getAge
method must take no parameters and must return a number. After the abstract methods have been implemented, we can call them normally like any other method.
Constructor Functions
When we declare a TypeScript, we’re declaring multiple things at the same time. We’re declaring an instance of the class, which is the entity with the code preceded with the class
keyword. For example, we can write:
class Employee{
name: string;
age: number;
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
}
let employee: Employee = new Employee('Jane', 20);
In the last line of the code snippet above, we’re using Employee
as the type of the instances of the class Employee
.
Also, we’re creating another value we call a constructor function. This is the function that’s called when the new
keyword is being used to create a new instance of the class.
If we compile the TypeScript code above to ES5 or earlier, we can see we get something like the following code generated:
"use strict";
var Employee = /** @class (function () {
function Employee(name, age) {
this.name = name;
this.age = age;
}
return Employee;
}());
var employee = new Employee('Jane', 20);
In the code above, the constructor function is the following code:
function Employee(name, age) {
this.name = name;
this.age = age;
}
We have this because in JavaScript, no matter what version we’re using, a class is just the syntactic sugar for constructor functions. The inheritance model of TypeScript just extends from JavaScript. It didn’t change the inheritance model of JavaScript since it’s supposed to be 100% compatible with existing JavaScript code so existing JavaScript code can be adopted to use TypeScript.
In TypeScript, we can get the type of the constructor function with the typeof
operator. It’s different from its usage in JavaScript as it has been extended to get the type of the constructor function of a class. If we run the following code …
class Employee{
name: string;
age: number;
static companyName: string = 'ABC Company';
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}}
let employeeConstructor: typeof Employee = Employee;
console.log(Employee.companyName);
console.log(employeeConstructor.companyName);
… it makes sense when we get the value of the static member companyName
logged in both console.log
statements. As we can see from the compiled output …
"use strict";
var Employee = /** @class */ (function () {
function Employee(name, age) {
this.name = name;
this.age = age;
}
Employee.companyName = 'ABC Company';
return Employee;
}());
var employeeConstructor = Employee;
console.log(Employee.companyName);
console.log(employeeConstructor.companyName);
… a JavaScript and TypeScript class are ultimately just functions. The class syntax makes inheritance of JavaScript easier to use since it looks like it’s using class-based inheritance, but it’s actually syntactic sugar on top of the prototypical inheritance model that’s been the same since JavaScript first came out.
TypeScript makes inheritance easy by letting us define abstract classes — where some implementation is done by the abstract and others are done in the subclass that extends the abstract class.
We also have abstract methods that subclasses can implement. Abstract methods only have the signature and return type and no implementation details.
Static members let us define members that are part of the class rather than an instance of the class.
It’s also important to know that the class syntax is ultimately syntactic sugar for the prototypical inheritance model that existed since the beginning of JavaScript. However, now we can put that aside since we can use the syntactic sugar to make it easier to understand and implement inheritance.