We can extend the Interfaces in TypeScript to include other interfaces. This helps us to create a new interface consisting of definitions from the other interfaces. Extending interfaces gives us more flexibility in how we define our interfaces and reuse the existing interfaces.
Table of Contents
Extending Interface
We extend an interface by using the extends keyword after the interface and name followed by a list of interfaces each separated by a comma.
This example Employee
interface extends the Address
interface. The employee object must contain all the properties from both the interface.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | interface Address { address: string; city:string state:string } interface Employee extends Address { firstName: string; lastName: string; fullName(): string; } let employee: Employee = { firstName : "Emil", lastName: "Andersson", fullName(): string { return this.firstName + " " + this.lastName; }, address:"India", city:"Mumbai", state:"Maharastra", } |
You can extend an interface from several other interfaces.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | interface Address { address: string; city:string state:string } interface Employment { designation: string; } interface Employee extends Address, Employment { firstName: string; lastName: string; fullName(): string; } let employee: Employee = { firstName : "Emil", lastName: "Andersson", fullName(): string { return this.firstName + " " + this.lastName; }, address:"India", city:"Mumbai", state:"Maharastra", designation:"CEO" } |
In this example, both Customer
and Employee
Interface extends the Person
interface which in turn extends an Address
interface.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 | interface Address { address: string; city:string state:string } interface Person extends Address { firstName: string; lastName: string; fullName(): string; } interface Customer extends Person { customeId:number } interface Employee extends Person { employeeId:number } let c:Customer= { customeId:100, firstName:"", lastName:"", fullName: function() {return this.firstName+" "+ this.lastName}, address:"", city:"", state:"" } let e:Employee= { employeeId:100, firstName:"", lastName:"", fullName: function() {return this.firstName+" "+ this.lastName}, address:"", city:"", state:"" } |
Common Properties are merged
If the Interface and the extended interfaces contain a common property, then their data type must be compatible with each other. In such cases they are merged else the compiler throws an error.
The BetterProduct
extends the Product
interface. Both the Interfaces contain a common property id
. Since the data type matches, the compiler merges both together and does not complain.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | interface Product { id: number; name:string } interface BetterProduct extends Product { id:number; price:number } let product:BetterProduct = { id:1, name:"Mobile", price:100 } |
But if you change the data type (id
is a string in BetterProduct
interface) as in this example, the compiler throws the error.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | interface Product { id: number; name:string } interface BetterProduct extends Product { id:string; price:number } let product:BetterProduct = { id:1, name:"Mobile", price:100 } //Compiler error //Interface 'BetterProduct' incorrectly extends interface 'Product'. // Types of property 'id' are incompatible. // Type 'string' is not assignable to type 'number'. |
Note that data types need not be the same. But they must be compatible with each other. In the example below the id
property has number
and any
data types. Since they are compatible with each other compiler does not complain.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | interface Product { id: number; name:string } interface BetterProduct extends Product { id:any; price:number } let product:BetterProduct = { id:1, name:"Mobile", price:100 } |
This example has id
property with unknown type & number data type. The number can be assigned to an unknown data type. Since the BetterProduct
extends id
from unknown to a number this code is acceptable.
1 2 3 4 5 6 7 8 9 10 11 | interface Product { id: unknown; name:string } interface BetterProduct extends Product { id:number; price:number } |
But unknown cannot be assigned to a number data type. Hence BetterProduct
cannot extend the number to an unknown type. This code throws an error.
1 2 3 4 5 6 7 8 9 10 11 | interface Product { id: number; name:string } interface BetterProduct extends Product { id:unknown; price:number } |
Common functions are merged if the signature is Compatible
Similar to the properties, the functions with the same name are merged only if they have compatible signatures. If the function signatures are not compatible then they are not merged and the compiler will throw an error.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | interface IOne { prop1:string, someFn():string } interface ITwo { address: string; someFn():number } interface IThree extends IOne, ITwo { } //Interface 'IThree' cannot simultaneously extend types 'IOne' and 'ITwo'. // Named property 'someFn' of types 'IOne' and 'ITwo' are not identical |
Readonly Properties
The extended interfaces can override the readonly and optional properties.
This example defines a read-only name property in the Person Interface. The Customer interface extends it and also declares the name property without the readonly modifier. This allows us the modify the name property on any object which is created from the Customer Interface.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | interface Person { readonly name: string; } interface Customer extends Person { name: string; //overrides the name from the person. customeId:number } let customer:Customer= { customeId:100, name:"Luis T. Mackey" } customer.name="Luis T. Mackey"; //Ok interface Employee extends Person { employeeId:number } let employee:Employee= { employeeId:100, name:"" } employee.name="Luis T. Mackey"; //Cannot assign to 'name' because it is a read-only property. |
The employee interface does not override the name property. Typescript compiler throws an error if any object created from the Employee Interface tries to modify the name property.
Interfaces Extending Classes
An Interface can also extend classes. The extended interface will include all class members (both public and private) but without any implementation.
In this example, the Employee
extends the Person
class and adds a new property Designation
. The newly created employee
must contain the name
property which is from the Person
class along with the Designation
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | class Person { public name:string; constructor(name:string) { this.name=name; } } interface Employee extends Person { Designation: string; } let employee:Employee= { name:"Jason B. McAtee", Designation:"Manager", } |
If the class contains a function, its implementation is not copied. The object created using the interface must provide its own implementation. The employee
object in the following example must implement the printName
function from the Person
class although the class contains its implementation.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | class Person { public name:string; constructor(name:string) { this.name=name; } printName() { console.log(this.name) } } interface Employee extends Person { Designation: string; } let employee:Employee= { name:"Jason B. McAtee", Designation:"Manager", printName() { console.log(this.name) } } |
Extending the class has one significant restriction. If the class from which we extend has private property (or protected property), then we can use the interface only with those classes which extend the class that the interface has extended.
For Example, we have added a private property (id
) to the Person class. The IEmployee
interface extends the Person
class. But when we create a new object employee
from the interface IEmployee
the compiler will throw the error.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | class Person { private id:number; public name:string; constructor(name:string) { this.id=0; this.name=name; } } interface IEmployee extends Person { Designation: string; } let employee:IEmployee= { name:"Jason B. McAtee", Designation:"Manager", } //Property 'id' is missing in type '{ name: string; Designation: string; }' but required in type 'Employee'. //'id' is declared but its value is never read. |
If we do not add the id
property to the employee object, the compiler complains that the Property 'id' is missing in type'
employee. But if we add the id
property, it still complains because the id
is a private property adding it to the employee object makes it public property.
Hence, the only way you can use it is to create a new class that extends the class (or subclasses of that class) from which the interface extends.
For Example, the IEmployee
interface extends the class Person
, which has private property. We can use the
interface only on those classes which extend from the IEmployee
Person
class (or any subclass of 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 25 26 27 28 29 30 | class Person { private id:number; public name:string; constructor(name:string) { this.id=1; this.name=name; } } interface IEmployee extends Person { Designation: string; } class Employee extends Person implements IEmployee { Designation:string constructor(name:string , designation:string) { super(name) this.Designation=designation; } } let employee = new Employee("Jason B. McAtee","Manager") |
Extending Interface Vs Merging Interface
Declaration Merging is another way by which you can extend an interface. This you can do by declaring the same interface again with new properties.
For Example, the following code is perfectly valid and the compiler does not throw any errors. First, we create Person
interface with only two properties. Later we add address
property to the Person
.
1 2 3 4 5 6 7 8 9 10 11 12 13 | interface Person { firstName: string; lastName: string; } //... //... interface Person { address:string } |
The typescript behind the scene merges both interfaces into one. This is known as Declaration Merging.
1 2 3 4 5 6 7 | interface Person { firstName: string; lastName: string; address:string } |
In interface merging the common properties with the same data type are merged. The common functions with the same function signatures are also merged.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | interface Product { id: number; name:string } interface Product { id:number; price:number } let product:Product = { id:1, name:"Mobile", price:100 } |
But the common functions with different signatures are overloaded. In this example, Product
interface declares the someFn
with different signatures. In the first interface, it takes a string as the argument and returns the string. While in the second interface it takes the number as the argument and returns the number.
Typescript merges both of them and creates an overload signature of the function. You can read more about function overloading.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | interface Product { id: number; name:string someFn(para:string) : string } interface Product { id:number; price:number someFn(para:number) : number } let product:Product = { id:1, name:"Mobile", price:100, someFn: function(para:string|number):any { return para; } } |