The Interface in TypeScript defines the Shape of an object. i.e. it specifies what properties & methods a given object can have and their corresponding value types. TypeScript interfaces are abstract types. They do not contain any code. An object, function, or class that implements the interface must implement all the properties specified in the interface. They are similar to type alias. Both allow us to name a type and use it elsewhere in the code.
Table of Contents
Creating an interface
We create an interface using the keyword interface followed by the interface name. It is then followed by curly brackets {}
. We define the shape of the object inside the curly brackets.
The following IProduct
interface defines the three properties and a method. Note that the interface doesn’t initialize nor implement the properties. It does not contain any code.
1 2 3 4 5 6 7 8 | interface IProduct { id:number, name:string, price:number, calculate:(qty:number)=> number } |
Once you have defined an interface, you can use it as a type. In the following example, we annotate the product variable with the IProduct
interface.
1 2 3 4 5 6 7 8 9 10 11 12 | let product: IProduct = { id : 1, name: "Samsung Galaxy", price:1000, calculate(qty:number): number { return this.price*qty; } } console.log(product.calculate(10)) //1000 |
If the Product variable does not contain any property or method defined in the Interface, then the Typescript compiler immediately throws an error.
For Example, if we remove the property price
from the product, then it will result in Property 'price' is missing in type
compiler error.
1 2 3 4 5 6 7 8 9 10 11 12 13 | let product: IProduct = { id : 1, name: "Samsung Galaxy", calculate(qty:number): number { return this.price*qty; } } //Property 'price' is missing in type ' //{ id: number; name: string; calculate(qty: number): number; }' //but required in type 'IProduct'. |
Hence the product object must adhere to an Interface. When it does that, we say that the object implements the interface.
JavaScript does not have Interfaces. Hence Interface definitions are not removed when the code is compiled to JavaScript.
Optional Properties
In the previous example, when we removed the property price from the product object, the compiler flagged it as an error. But in real life, there could be situations where we may require to create objects without assigning values to all properties.
In such cases, we can mark the property as optional with a question mark after the name.
In this example, address is optional. The compiler does not throw any errors.
1 2 3 4 5 6 7 8 9 10 11 12 | interface IEmployee { firstName: string; lastName: string; address?:string } let employee:IEmployee= { firstName: "Allie", lastName: "Grater", } |
Read-Only Properties
We can also mark the property as read-only by prefixing the property as readonly
. When the property is marked as read-only, we can assign a value to the property only when initializing the object or in the class constructor. Any subsequent assignments will result in a compiler error.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | interface IEmployee { readonly firstName: string; readonly lastName: string; address:string } let employee:IEmployee= { firstName: "Allie", lastName: "Grater", address:"Mumbai, India" } employee.firstName="Bill" //Compiler Error employee.address="Mumbai, Maharastra India" //ok |
Further reading Read-only Properties in TypeScript
Interface for an array of objects
The following examples show how you can use represent an array of objects using the interface.
The simplest option is to create the interface for the object and use the [] to declare the variable.
1 2 3 4 5 6 7 8 9 10 11 | interface Employee { id: number; name: string; } const arrEmployee: Employee[] = [ { id: 1, name: 'Rebecca' }, { id: 2, name: 'Akins' }, ]; |
You can also create another interface to extend the Array interface as shown below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | interface Employee { id: number; name: string; } interface EmployeeArray extends Array<Employee> {} let arrEmployee: EmployeeArray = [ { id: 1, name: 'Rebecca' }, { id: 2, name: 'Akins' }, ]; |
Interfaces for functions
Interfaces are also capable of describing the functions.
To create an interface for function type use the keyword interface followed by the interface name. Then follow It up with curly brackets {}
. Inside the curly braces { } add the list of parameters (comma-separated) inside parentheses. Follow it up with a semicolon :
and return type.
This example shows how you can create an interface for a function. The IAdd
interface declares a function with two arguments.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | //interface interface IAdd { (arg1:number,arg2:number):number } //implementation let add:IAdd = function(num1:number,num2:number) { return num1+num2; } //invoking add(10,20) //30 |
Typescript compiler throws an error if we try to use more arguments or arguments of incompatible data types to the implementation function.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | interface IAdd { (arg1:number,arg2:number):number } let add:IAdd add=function(num1:number,num2:number,num3:number) { return num1+num2; } //Type '(num1: number, num2: number, num3: number) => number' is not assignable to type 'IAdd'. //invoking add(10,20) //30 |
But allows less number of arguments. In this example, arg2
is missing in the implementation function and yet the compiler will not complain.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | interface IAdd { (arg1:number,arg2:number):number } let add:IAdd add=function(num1:number) { //No error return num1; } //invoking add(10,20) //30 |
This example shows how you can create an interface for types that contain a function. The signature of the getName
function inside the IEmployee
type in declared inline.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | interface IEmployee { firstName: string; lastName:string getName(fName:string,lName:string):string } let emp:IEmployee= { firstName:"", lastName:"", getName:function(fName:string,lName:string) { return fName+lName } } |
You can also create a separate interface for the function and use it to annotate the getName
function.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | interface IgetName {(fName:string,lName:string):string} //function interface interface IEmployee { firstName: string; lastName:string; getName:IgetName; } let emp:IEmployee= { firstName:"", lastName:"", getName:function(fName:string,lName:string) { return fName+lName } } |
Function Overloading
Functions with the same name but with different parameter types will result in the creation of an overloaded function. Read more about it from Function Overloading
This example has someFunc
declared twice but with different arguments and return types. This is known as function overloading.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | interface IProduct { id: number; name:string someFunc(arg1:string):string someFunc(arg1:number):number; } let product:IProduct = { id:1, name:"Mobile", someFunc(arg1:any) { return arg1; }, } |
Class Implementing Interface
The Interfaces can be used with the Classes using the keyword implements
.
The following example creates the Employee class which implements the IEmployee
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 | interface IEmployee { firstName: string; lastName: string; address:string; getName():string } class Employee implements IEmployee { firstName: string; lastName: string; address:string; constructor(firstName: string, lastName: string, address:string) { this.firstName = firstName; this.lastName = lastName; this.address=address; } getName():string { return this.firstName+' '+this.lastName } } |
We will learn more about the classes in the subsequent tutorials.
Extending Interfaces
We can extend the Interfaces to include other interfaces. This helps us to create a new interface consisting of definitions from the other interfaces
This example IEmployee
interface also extends the IAddress
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 26 | interface IAddress { address: string; city:string state:string } interface IEmployee extends IAddress { firstName: string; lastName: string; fullName(): string; } //Compiler error let employee: IEmployee = { 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 31 | interface IAddress { address: string; city:string state:string } interface IEmployment { designation: string; } interface IEmployee extends IAddress, IEmployment { firstName: string; lastName: string; fullName(): string; } //Compiler error let employee: IEmployee = { firstName : "Emil", lastName: "Andersson", fullName(): string { return this.firstName + " " + this.lastName; }, address:"India", city:"Mumbai", state:"Maharastra", designation:"CEO" } |
Note that if the properties with the same name and data type are merged. In the case of functions, the function signatures must match. i.e. number of arguments, their data type, and function return type must be compatible.
Interface Merging
Declaration merging is one of the unique concepts of TypeScript. “declaration merging” means that the compiler merges two or more separate declarations declared with the same name into a single definition.
This example creates two interfaces with the name IEmployee
.
1 2 3 4 5 6 7 8 9 10 11 12 13 | interface IEmployee { firstName: string; lastName: string; fullName(): string; } interface IEmployee { address: string; city:string state:string } |
The compiler does not complain, but instead, it merges both the interface into one.
1 2 3 4 5 6 7 8 9 10 11 | //merged to interface IEmployee { firstName: string; lastName: string; fullName(): string; address: string; city:string state:string } |
That is why the following code results in a compiler error because the employee object does not implement the address
, city
and state
property.
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 | interface IEmployee { firstName: string; lastName: string; fullName(): string; } interface IEmployee { address: string; city:string state:string } //Compiler error let employee: IEmployee = { firstName : "Emil", lastName: "Andersson", fullName(): string { return this.firstName + " " + this.lastName; } } //Type '{ firstName: string; lastName: string; fullName(): string; }' //is missing the following properties //from type 'Employee': address, city, state |
In the case of interface merging, the common properties with the same name and data type are merged. But in the case of functions, if the function signatures do not match, then overload functions are created. This is different from interface extending where it compiler throws an error.
Generic interfaces
The generics in typescript allow us to work with a variety of types instead of a single one.
In this example, we have created two interfaces IaddString
& IaddNum
. Both take two arguments and add them and return the result.
1 2 3 4 5 6 7 8 9 10 11 12 13 | interface IaddString {(arg1:string,arg2:string):string} interface IaddNum {(arg1:number,arg2:number):number} let addString:IaddString= function(arg1:string,arg2:string) { return arg1+arg2; } let addNum:IaddNum= function(arg1:number,arg2:number) { return arg1+arg2; } |
Using generics, we can reduce the number of interfaces into a single one. Here T represents a type. It could be anything string, number or array, etc. Here a single interface handles multiple related functions.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | interface Iadd<T> {(arg1:T,arg2:T):T} let addString1:Iadd<string> = function(arg1:string,arg2:string) { return arg1+arg2; } let addNum1:Iadd<number> = function(arg1:number,arg2:number) { return arg1+arg2; } let addArray:Iadd<String[]> = function(arg1:String[],arg2:String[]) { return [ ...arg1, ...arg2]; } |