How is Declaration Merging done in TypeScript?


Introduction

TypeScript offers powerful features that enhance JavaScript development. One such feature is declaration merging, which allows developers to combine multiple declarations of the same entity into a single definition. This tutorial will introduce you to the concept of declaration merging in TypeScript and provide examples to help you understand its practical implementation.

Declaration Merging Basics

Declaration merging in TypeScript enables the compiler to merge multiple declarations for the same entity, such as interfaces, functions, classes, or enums. By merging declarations, you can extend existing types and add new properties, methods, or functionality.

Let's explore the scenarios where declaration merging can be useful and understand the syntax and examples associated with each scenario.

Syntax

interface User {
   var1: string;
   var2: number;
}
interface User {
   var3: string;
}

The above is the syntax to do declaration merging in typescript. We declare the interface using the ‘interface’ keyword and then again declare it using the same interface name to merge it.

Example 1: Merging Interfaces

Interfaces define the structure of objects in TypeScript. With declaration merging, you can extend an existing interface with additional properties or methods. By merging the User interface declarations, we extend its definition to include the email property. As a result, the user object can contain all three properties defined in the merged interface.

In the example below, we have defined an interface User with name and age properties. Later, we merge the User interface with another declaration that adds an email property. The resulting merged interface allows us to create objects that satisfy both sets of properties.

interface User {
   name : string;
   age : number;
}
interface User {
   email : string;
}
const user: User = {
   name : "Manta Ray",
   age : 32,
   email : "mantaray@example.com",
};
console.log(user);

On compiling, it will generate the following JavaScript code −

var user = {
   name: "Manta Ray",
   age: 32,
   email: "mantaray@example.com"
};
console.log(user);

Output

The above code will produce the following output −

{ name: 'Manta Ray', age: 32, email: 'mantaray@example.com' }

Example 2: Merging Functions

Declaration merging is not limited to interfaces; it also applies to functions. When merging function declarations, TypeScript allows you to combine multiple function signatures into a single overloaded function. In the example above, we define two function signatures for the greet function: one that accepts a string parameter and another that accepts a number parameter. The implementation of the function includes a conditional statement to handle each parameter type accordingly. By merging the function declarations, we create an overloaded function that can handle both string and number arguments.

function greet(name : string): void;
function greet(age : number): void;
function greet(param: string | number): void {
   if (typeof param === "string") {
      console.log(`Hello, ${param}!`);
   } else if (typeof param === "number") {
      console.log(`You are ${param} years old.`);
   }
}
greet("John");
greet(25); 

On compiling, it will generate the following JavaScript code −

function greet(param) {
   if (typeof param === "string") {
      console.log("Hello, ".concat(param, "!"));
   }
   else if (typeof param === "number") {
      console.log("You are ".concat(param, " years old."));
   }
}
greet("John");
greet(25);

Output

The above code will produce the following output −

Hello, John!
You are 25 years old.

Example 3: Merging Namespaces

Namespaces in TypeScript allow developers to organize their code into logical containers. Declaration merging enables merging namespaces to add new members or extend existing ones. In this example, we define two namespaces, Utilities, each containing a function. By merging the namespaces, we create a single namespace Utilities containing the formatDate and formatTime functions. As a result, we can access and use both functions without any conflicts.

namespace Utilities {
   export function formatDate(date: Date): string {
      return `${date.getDate()}/${date.getMonth() + 1}/${date.getFullYear()}`;
   }
}
namespace Utilities {
   export function formatTime(date: Date): string {
      return `${date.getHours()}:${date.getMinutes()}`;
   }
}
const formattedDate = Utilities.formatDate(new Date());
const formattedTime = Utilities.formatTime(new Date());
console.log(formattedDate);
console.log(formattedTime);

On compiling, it will generate the following JavaScript code −

var Utilities;
(function (Utilities) {
   function formatDate(date) {
      return "".concat(date.getDate(), "/").concat(date.getMonth() + 1, "/").concat(date.getFullYear());
   }
   Utilities.formatDate = formatDate;
})(Utilities || (Utilities = {}));
(function (Utilities) {
   function formatTime(date) {
      return "".concat(date.getHours(), ":").concat(date.getMinutes());
   }
   Utilities.formatTime = formatTime;
})(Utilities || (Utilities = {}));
var formattedDate = Utilities.formatDate(new Date());
var formattedTime = Utilities.formatTime(new Date());
console.log(formattedDate);
console.log(formattedTime);

Output

The above code will produce the following output −

31/5/2023
17:44

Example 4: Merging Enums

Enums provide a way to define a set of named constants. Declaration merging allows us to extend an existing enum with additional values. In this example, we define an enum Colors with two values: Red and Green. Later, we merge the Colors enum with an additional value, Blue. The resulting merged enum allows us to use all three values, and we can assign and store enum values as variables or in arrays.

enum Colors {
   Red = "RED",
   Green = "GREEN",
}

enum Colors {
   Blue = "BLUE",
}

const favoriteColor: Colors = Colors.Green;
const allColors: Colors[] = [Colors.Red, Colors.Green, Colors.Blue];
console.log(favoriteColor);
console.log(allColors);

On compiling, it will generate the following JavaScript code −

var Colors;
(function (Colors) {
   Colors["Red"] = "RED";
   Colors["Green"] = "GREEN";
})(Colors || (Colors = {}));
(function (Colors) {
   Colors["Blue"] = "BLUE";
})(Colors || (Colors = {}));
var favoriteColor = Colors.Green;
var allColors = [Colors.Red, Colors.Green, Colors.Blue];
console.log(favoriteColor);
console.log(allColors);

Output

The above code will produce the following output −

GREEN
[ 'RED', 'GREEN', 'BLUE' ]

Conclusion

Declaration merging in TypeScript is a powerful feature that enhances code organization and extensibility. By merging declarations, you can seamlessly extend existing types, interfaces, functions, namespaces, or enums without conflicts. This tutorial introduced you to the concept of declaration merging and provided examples for merging interfaces, functions, namespaces, and enums. Remember to utilize this feature judiciously, maintaining code readability and avoiding ambiguity.

Updated on: 21-Aug-2023

542 Views

Kickstart Your Career

Get certified by completing the course

Get Started
Advertisements