The Classes and Inheritance in Typescript

April 24, 2020

Few days, ago I was playing with classes in Typescript and see how it help me to keep code control and use some features like inheritance and access modifiers in my code.

If you are working with ES6 or ESNEXT maybe you are used to classes. In C#, Java, or any language with OOP the classes are a blueprint with properties and behavior to represent entities.

Create a class

The classes are defined using the keyword class, the class contains fields it allows to store data into the class.

class Student {
   name: string;
}

Constructor

The constructor is a function and reserved word for JavaScript, used for initialization of objects and return a class instance.

class Student {
   name: string;
  constructor(name:string) {
       this.name = name;
 }
}

The constructor run when creating a class instance using the new keyword.

let universityStudent = new Student('dany');

If we compile the code, the tsc compiler generates a different code if it is ES5, ES6, or ESNEXT.

ES5 result doesn’t support class keyword, the result is the way to build objects in vanilla Javascript.

"use strict";
var Student = /** @class */ (function () {
   function Student(name) {
       this.name = name;
       console.log("Hello " + name);
   }
   return Student;
}());
var universityStudent = new Student("dany");

ES6 result is similar but doesn’t have the type because types is part of Typescript, not JavaScript.

"use strict";
class Student {
   constructor(name) {
       this.name = name;
       console.log(`Hello ${name}`);
   }
}
let universityStudent = new Student("Edgar");

The instance return an object with key-value pairs and the key is name and the value is Edgar.

Hello Edgar
Student2 { name: 'Edgar' }

Access to instance fields with this..

The fields into our class must be access using this keyword, because if not, the instance will find the field in the global scope.

For example, the class student has the hello method and shows the value of the name field in the Student class but another global variable named name already exists.

let name = "Dany";
class Student {
  name: string;
  constructor(name: string) {
     this.name = name;
  }
  hello() {
     return `Hello my friend ${name}`;
  }
}
let universityStudent = new Student("Edgar");
let result = universityStudent.hello();
console.log(result);

The result.

[nodemon] starting `node .\Student.js`
Hello my friend Dany

The hello method finds for the variable name, but into the global space, not the current instance scope, change to use this keyword with this.name and the hello method will find into the current scope.

[nodemon] starting `node .\Student.js`
Hello my friend Edgar

Access modifiers

Typescript supports access modifiers as private, public, protected and readonly in development. It helps us to write code with a clear intention and avoid unexpected behaviors.

For example, my manager requested new changes for the Student entity class. Please add roles to Students, don’t allow to add the ADMIN role and generate a unique ID.

My solution is add id ,role fields and the addRole method with a validation.

let name = "Dany";
class Student {
  name: string;
  id: number;
  roles: [];
  constructor(name: string) {
     this.name = name;
     this.id = Math.random();
  }
  hello() {
     return `Hello my friend ${name}`;
  }
  addRole(role) {
     if(role !== "ADMIN") {
        this.roles.push(role);
     }
  }
}

My code will work, but has some weaknesses if other developers try to use it.

let edgar = new Student("Edgar");
edgar.addRoles("ADMIN");
//Other developer using class Student!!!
edgar.roles.push("ADMIN");
edgar.id = 1;
console.log(edgar);
[nodemon] starting `node .\Student.js
ADMIN IS NOT ALLOWED
Student { name: 'Edgar', id: 1, roles: [ 'ADMIN' ] }

The validation shows the error, but the role has been added, and the id changed. It happened because by default all fields in JavaScript are public.

Using the following typescript access modifier we can fix the issue.

private: The field only can be changed inside the class instance.

readonly: The field only can be set on the declaration or into the constructor.

public: The field doesn’t have restrictions and can be modified from any place.

protected: The field can change inside and derived class from it.

class Student {
  public name: string;
  readonly id: number;
  private roles: string[];
  constructor(name: string) {
     this.name = name;
     this.id = Math.random();
     this.roles = [];
  }
  hello() {
     return `Hello my friend ${this.name}`;
  }
  addRoles(role: string) {
     if (role !== "ADMIN") {
        this.roles.push(role);
     } else {
        console.log(`${role} IS NOT ALLOWED`);
     }
  }
}

With these changes, the compiler and IDE raise and show errors because the code has restrictions.

  • Property ‘roles’ is private and only accessible within Student class
  • Cannot assign to ‘id’ because it is a read-only property.

Inheritance

Typescript supports inheritance between classes, for reuse of methods, props, and behaviors from a base class using the extends keyword.

For example, we need to create a new class for UniversityStudent with name, id, university, and roles fields. Some of these fields already exist in the Student class, it is a perfect case for use inheritance.

The using the extends keyword before the name class name and the name of receives class in my case Student.

The UniversityStudent class inherits nonprotected properties and methods from the Student class and also the constructor, then we can create new objects using the default constructor from the Student class.

Tip: You cannot inherit from more than one class

class UniversityStudent extends Student {
   public university: string;
}

let student = new UniversityStudent("dany");

student.addRoles("READER")
console.log(student)

The UniversityStudent class can have its own constructor with some logic for the university field with 2 parameters: name and university, the name used by the base class Student, and university for the university field.

We must call the base class constructor, using the function super() and passing the parameters required.

class UniversityStudent extends Student {
   public university: string;
  constructor(name: string, university: string) {
      super(name)
      this.university = university.length > 0 ? university : "UB";
  }
}

let studentUB = new UniversityStudent("dany","");
let student = new UniversityStudent('edgar', 'UASD')
student.addRoles("READER")
console.log(student)
console.log(studentUB)

Protected

The private properties are only accessed within a defined class, not from inheritance. That means the UniversityStudent class cannot read the roles field.

The protected access modifier allows the field to only be accessed and modified by a defined class or class which inherits from it.

The roles field in the Student class changes the access modifier to protected then UniversityStudent class can access and change the roles field.

class Student {
  public name: string;
  readonly id: number;
  protected roles: string[];
  constructor(name: string) {
     this.name = name;
     this.id = Math.random();
     this.roles = [];
  }
  hello() {
     return `Hello my friend ${this.name}`;
  }
  addRoles(role: string) {
     if (role !== "ADMIN") {
        this.roles.push(role);
     } else {
        console.log(`${role} IS NOT ALLOWED`);
     }
  }
}
class UniversityStudent extends Student {
    public university: string;
   constructor(name: string, university: string) {
       super(name)
       this.university = university.length > 0 ? university : "UB";
   }
   deleteRoles(){
       this.roles = []
   }
}

let student = new UniversityStudent('edgar', 'UASD')
student.addRoles("READER")
console.log(student)
student.deleteRoles();
console.log(student);

The deleteRoles can remove the roles field.

[nodemon] starting `node app.js estudiante.js`
UniversityStudent {
  name: 'edgar',
  roles: [ 'READER' ],
  id: 0.24478370725216325,
  university: 'UASD'
}
UniversityStudent {
  name: 'edgar',
  roles: [],
  id: 0.24478370725216325,
  university: 'UASD'
}

That gives to you a bit of a head start with class and inherits in Typescript. If you enjoyed this post, please share :).