Inheritance in TypeScript enables you to create a new class by using the existing class. The new class will reuse the properties and methods of the existing class. It may also modify the behavior of the existing class by adding its own properties and methods. Inheritance can be single-level or multi-level. But a Class can extend only from a single Class. In this tutorial, we will learn inheritance in Typescript. Learn method overriding, property overriding, etc. Also look at the effects of inheritance on the visibility of the properties like public, private, protected, etc. We also learn how to override readonly & optional properties etc
Table of Contents
What is Inheritance
Inheritance refers to the ability of an object to reuse the properties and methods of other objects.
The main purpose of the inheritance is to re-use code. For Example, the Cat
, Dog
, etc have many common characteristics (properties & methods) in common. Those common properties & methods can be clubbed in a parent class Animal
. The Cat & Dog classes can both use these properties & methods from the Parent class. Here the Animal Class is the Parent of the Cat & Dog class, Hence known as the Parent class or Superclass. The Cat & Dog classes are known as Child or Subclass.
Creating Inheritance in TypeScript
To create inheritance we use the extends keyword
The syntax for extending a class is as follows. We start with declaring a new class with the class keyword followed by the name of the class. Next, we use the extends keyword and specify the name of the class which we want to extend. The Class from which we extend is also known as Parent, Base, or Super Class. The extended class is known as child or sub-class.
1 2 3 4 5 6 7 | class <ClassName> extends <ParentClassName> { // methods and fields { |
In the following example, Employee Class declares its own property designation
. It also uses the extends keyword to extend from the Person class. Hence it gets all the properties and methods from the Person Class. You can see from the example that we can access the firstName
, lastName
, and getName
method from the instance of the Employee
class. These properties and methods come from Parent class i.e. Person
class
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | class Person { firstName:string=""; lastName:string=""; getName() :string { return this.firstName+ " "+this.lastName } } class Employee extends Person{ designation:string=""; } //extend class gets all the properties and methods of the Parent Class let e= new Employee(); e.firstName="Jon" e.lastName="Snow" e.designation="Lead Cast" console.log(e.getName()) //"Jon Snow" console.log(e) // Employee: { "firstName": "Jon", "lastName": "Snow", "designation": "Lead Cast"} |
Invoking Parent class constructor
In the above example, we used the simple class without a constructor
If the parent class has a constructor, then the child class must invoke the parent class constructor in its constructor. We do that by using a special method super
.
In the example below, the Person class has a constructor method that accepts two arguments. firstName
& lastName
.
1 2 3 4 5 6 7 8 9 10 11 12 | class Person { firstName:string; lastName:string; constructor(firstName:string, lastName:string) { this.firstName=firstName; this.lastName=lastName; } } |
The Employee
class extends the Person
class. Since the Person
class has a constructor method, we need to invoke the super
method in the constructor of the Employee
class. The Person
constructor accepts firstName
& lastName
arguments. Hence we need to pass those as parameters to the super
method.
1 2 3 4 5 6 7 8 9 10 11 12 13 | class Employee extends Person { designation:string; constructor(firstName:string, lastName:string, designation:string) { super(firstName,lastName) // call parent class constructor this.designation=designation; } } |
The call to super
must be made before the use of ‘this’ keyword.
In this example, we try to access the this
before we call the super method. The compiler flags this as an error (“‘super’ must be called before accessing ‘this’ in the constructor of a derived class“).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | class Employee extends Person { designation:string; constructor(firstName:string, lastName:string, designation:string) { console.log(this) //'super' must be called before accessing 'this' in the constructor of a derived class. super(firstName,lastName) // call parent class constructor this.designation=designation; } } |
If we do not call the super method, then the compiler flag this as an (“Constructors for derived classes must contain a ‘super’ call“) error.
1 2 3 4 5 6 7 8 9 10 11 | class Employee extends Person { designation:string; constructor(firstName:string, lastName:string, designation:string) { } } //Constructors for derived classes must contain a 'super' call. |
If the child class does not need a constructor, then you can omit to call super method. The compiler will not flag this as an error.
1 2 3 4 5 | class Employee extends Person { designation:string=""; } |
But the Typescript automatically creates a constructor method with a super call in the generated JavaScript. The above results in the following JavaScript code.
1 2 3 4 5 6 7 8 9 | class Employee extends Person { constructor() { super(...arguments); this.designation = ""; } } |
Remember that
- The call to
super
must be made before the use of ‘this’ keyword. - If the derived class has a constructor method, then calling
super
is compulsory. - In case the parent class does not have a constructor method, then invoke
super
without any parameter - If both parent and child do not need a constructor method, then you do not have to create one. JavaScript will automatically create an empty constructor for both.
Method Overriding
You can override the Methods & Properties of the Parent class in the child class.
In method overriding the types of the parameters and the return type of the method have to be compatible with that of the parent class.
While in Property overriding the data type of the Property have to be compatible with that of the parent class
In this example, The Employee
class overrides the greet
method of the parent
class.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | class Person { greet(name:string) :void { console.log("Hello from Parent "+ name) } } class Employee extends Person{ greet(name:string) :void { console.log("Hello from Child "+ name) } } let e = new Employee() e.greet("Jon Snow") //Hello from Child Jon Snow |
You can use the super
to invoke the parent class method. In this example super.greet()
method invokes the greet
method from the parent class.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | class Person { greet(name:string) :void { console.log("Hello from Parent "+ name) } } class Employee extends Person{ greet(name:string) :void { super.greet(name) //invoke parent class method console.log("Hello from Child "+ name) } } let e = new Employee() e.greet("Jon Snow") //Hello from Parent Jon Snow //Hello from Child Jon Snow |
We cannot make the greet
method incompatible with the parent class greet
method. In this example, we have added another parameter message
to the greet
method. This will result in a compiler error as the parent class greet
method does not have the parameter message
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | class Person { greet(name:string) :void { console.log("Hello from Parent "+ name) } } class Employee extends Person{ greet(name:string, message:string) :void { super.greet(name) console.log(message +" from Child "+ name) } } //Property 'greet' in type 'Employee' is not assignable to the same property in base type 'Person'. // Type '(name: string, message: string) => void' is not assignable to type '(name: string) => void'. |
But you can make message
as optional. This will make the greet
method compatible with the parent method. Hence it will not result in an error.
1 2 3 4 5 6 7 8 | class Employee extends Person{ greet(name:string, message?:string) :void { super.greet(name) console.log(message +" from Child "+ name) } } |
You can also return string
from the child greet
method. The parent greet
method returns
. Although the void
void
is not compatible with the string
compiler does not raise any errors. This is because If a function specifies a return type as void
can return any other value, the compiler will ignore it. You can refer to void Vs undefined for more.
1 2 3 4 5 6 7 8 9 | class Employee extends Person{ greet(name:string, message?:string) :string { //Return type is string super.greet(name) console.log(message +" from Child "+ name) return message +" from Child "+ name } } |
But you cannot use incompatible return types. The greet
method in the Parent class returns a string
, while the child returns a number
. Since these two types are incompatible the compiler throws an error.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | class Person { greet(name:string) :string { console.log("Hello from Parent "+ name) return "Hello from Parent "+ name } } class Employee extends Person{ greet(name:string, message?:string) :number { super.greet(name) console.log(message +" from Child "+ name) return 10 } } |
Similarly, you can override a property, as long as the data types are compatible. The following example throws an error because the data type of age
property is incompatible
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | class Person { age:number=0 } class Employee extends Person{ age:string="" //not allowed } //Property 'age' in type 'Employee' is not assignable to the same property in base type 'Person'. // Type 'string' is not assignable to type 'number'. |
Inheritance & Visibility
Typescript does not allow us to change the visibility of the properties in the overridden class except for changing protected to public.
The following code throws an error because we are changing visibility from public
to private
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | class Person { public age:number=0 } class Employee extends Person{ private age:number=0 } //Class 'Employee' incorrectly extends base class 'Person'. //Property 'age' is private in type 'Employee' but not in type 'Person'. |
But changing from protected
to public
is allowed. You can read more about the Exposure of protected members
1 2 3 4 5 6 7 8 9 10 | class Person { protected age:number=0 } class Employee extends Person{ public age:number=0 } |
Multi-level Inheritance
Inheritance in typescript can go up to any level. The following is an example of three-level of inheritance.
The PermanentEmployee
inherits from the Employee
class which in turn inherits from the Person
class.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | class Person { firstName:string="" lastName:string="" } class Employee extends Person{ Id:number=0; designation:string="" } class PermanentEmployee extends Employee{ division:string="" } let e = new PernanentEmployee() |
Typescript does not allow us to inherit from more than one class. The following code results in an error as the Employee
class trying to extend from both Person
& Address
class.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | class Person { firstName:string="" lastName:string="" } class Address { Address:string="" } class Employee extends Person, Address{ id:string="" } //Classes can only extend a single class. |
Overriding Readonly & Optional Properties
The derived classes can override the Readonly and Optional properties of Public and Protected Properties. It can either make the property Readonly and/or Optional or Remove it
In the following example, price property in the Product class is readonly. The derived ITProduct class re declares the price
property without the readonly prefix effectively making it both read & write
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | class Product { name:string readonly price:number constructor(name:string, price:number) { this.name=name this.price=price } } class ITProduct extends Product { price:number constructor(name:string, price:number) { super(name,price) this.price=price } } let p = new ITProduct("Computer",1000) p.price=2000 // ok |
Similarly, the derived class removes the optional from the price
property.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | class Product { name:string price?:number constructor(name:string) { this.name=name } } class ITProduct extends Product { price:number constructor(name:string) { super(name) } } //Property 'price' has no initializer and is not definitely assigned in the constructor. |