TypeScript - Extending Interfaces



In TypeScript, interfaces are a powerful way to define the shape of objects and enforce type constraints. They allow us to specify the required properties and methods that an object must have. One interesting feature of interfaces is the ability to extend them, allowing us to create a combination of interfaces. Extending an interface is also known as interface inheritance.

Interface inheritance allows you to create more specialized interfaces that inherit properties and methods from other interfaces.

Syntax

Use extends keyword to extend a single or multiple interfaces in TypeScript.

interface NewInterface extends ExistingInterface {
   // Additional properties and methods
}

interface NewInterface extends Interface1, Interface2, ... {
   // Additional properties and methods
}

The syntax to create a new interface by extending an existing one is shown above. The syntax for extending multiple interfaces is also shown. The second syntax is to extend multiple interfaces.

Extending a Single Interface

We can achieve single interface inheritance by extending a single interface. Let's start with a simple scenario where we have an interface called Shape that defines a common property color for various shapes. We want to create a new interface ColoredShape that extends Shape and adds an additional property name.

Example

Let's see an example of extending the Shape interface to create the ColoredShape interface. In this example, we defined the Shape interface with a color property. Then, we created the ColoredShape interface by extending Shape and adding a name property. We instantiated an object square of type ColoredShape and assigned values to color and name properties. Finally, we accessed and printed the values of color and name using dot notation.

interface Shape {
   color: string;
}

interface ColoredShape extends Shape {
   name: string;
}

const square: ColoredShape = {
   color: "red",
   name: "Square",
};

console.log(square.color);
console.log(square.name); 

On compiling, it will generate the following JavaScript code:

var square = {
   color: "red",
   name: "Square"
};
console.log(square.color);
console.log(square.name);

Output

red
Square

Extending Multiple Interfaces

In TypeScript, we can also extend multiple interfaces to create a new interface that combines properties and methods from all the extended interfaces. This allows us to create more complex and specialized interfaces by reusing existing ones. This helps to achieve multiple interface inheritance in TypeScript.

Example

In the example below, we defined the Printable and Scanable interfaces, each with their respective methods. Then, we created the MultifunctionalDevice interface by extending both Printable and Scanable interfaces and adding a new method copy(). We implemented the MultifunctionalDevice interface in a class called Printer and provided the necessary implementations for all the methods. Finally, we instantiated an object of the Printer class and called the print(), scan(), and copy() methods.

interface Printable {
   print(): void;
}

interface Scanable {
   scan(): void;
}

interface MultifunctionalDevice extends Printable, Scanable {
   copy(): void;
}

class Printer implements MultifunctionalDevice {
   print() {
      console.log("Printing...");
   }

   scan() {
      console.log("Scanning...");
   }

   copy() {
      console.log("Copying...");
   }
}

const printer = new Printer();
printer.print(); 
printer.scan(); 
printer.copy(); 

On compiling, it will generate the following JavaScript code:

var Printer = /** @class */ (function () {
   function Printer() {
   }
   Printer.prototype.print = function () {
      console.log("Printing...");
   };
   Printer.prototype.scan = function () {
      console.log("Scanning...");
   };
   Printer.prototype.copy = function () {
      console.log("Copying...");
   };
   return Printer;
}());
var printer = new Printer();
printer.print();
printer.scan();
printer.copy();

Output

Printing...
Scanning...
Copying...

Enhancing an Existing Interface

We may often encounter situations where we want to enhance an existing interface by adding additional properties or methods. Extending the interface allows us to do so without modifying the original interface directly.

Example

In this example, we have an existing User interface with a name property. We extend the User interface to create an EnhancedUser interface, which adds an age property and a greet() method. By extending the interface, we can define an object user of type EnhancedUser that includes the properties and methods from both interfaces.

interface User {
   name: string;
}

interface EnhancedUser extends User {
   age: number;
   greet(): void;
}

const user: EnhancedUser = {
   name: "John Wick",
   age: 25,
   greet() {
      console.log(`Hello, my name is ${this.name} and I'm ${this.age} years old.`);
   }
};

user.greet();

On compiling, it will generate the following JavaScript code:

var user = {
   name: "John Wick",
   age: 25,
   greet: function () {
      console.log("Hello, my name is ".concat(this.name, " and I'm ").concat(this.age, " years old."));
   }
};
user.greet();

Output

Hello, my name is John Wick and I'm 25 years old.

Creating Composite Interfaces

Extending interfaces can also be valuable when creating composite interfaces that combine properties and methods from multiple sources. This is particularly useful when working with external libraries or modules that provide their interfaces.

Example

In this example, we have four interfaces: Product, DiscountedProduct, ProductWithReviews, and FeaturedProduct. Each interface extends one or more existing interfaces, allowing us to create a composite interface with properties and methods from multiple sources. We then define an object product of the type FeaturedProduct, which incorporates all the properties defined in the extended interfaces.

interface Product {
   name: string;
   price: number;
}

interface DiscountedProduct extends Product {
   discount: number;
}

interface ProductWithReviews extends Product {
   reviews: string[];
}

interface FeaturedProduct extends DiscountedProduct, ProductWithReviews {
   featured: boolean;
}

const product: FeaturedProduct = {
   name: "Smartphone",
   price: 599,
   discount: 50,
   reviews: ["Great product!", "Highly recommended."],
   featured: true
};

console.log(product.featured); 
console.log(product.reviews);

On compiling, it will generate the following JavaScript code:

var product = {
   name: "Smartphone",
   price: 599,
   discount: 50,
   reviews: ["Great product!", "Highly recommended."],
   featured: true
};
console.log(product.featured);
console.log(product.reviews);

Output

true
[ 'Great product!', 'Highly recommended.' ]

Overriding Properties and Methods

We can also override properties and methods inherited from the base interfaces when extending interfaces. This allows us to modify or provide different implementations for specific properties or methods in the extended interface.

Example

In the below example, we have an Animal interface with a name property and a makeSound() method. We extend the Animal interface to create a Dog interface. By overriding the makeSound() method in the Dog interface, we provide a different implementation specific to dogs. The object dog of type Dog can then be instantiated, and the overridden method will be invoked when called.

interface Animal {
   name: string;
   makeSound(): void;
}

interface Dog extends Animal {
   makeSound(): void;
}

const dog: Dog = {
   name: "Buddy",
   makeSound() {
      console.log("Woof woof!");
   }
};

dog.makeSound();

On compiling, it will generate the following JavaScript code:

var dog = {
   name: "Buddy",
   makeSound: function () {
      console.log("Woof woof!");
   }
};
dog.makeSound();

Output

Woof woof!
Advertisements