TypeScript declaration merging

In TypeScript, when two separate declarations with the same name are being merged into a single definition, it is called declaration merging. It is a good practice to be aware of, for example, when you need to extend type declarations from a third-party library. Among other reasons to use TypeScript, understanding it might also be helpful in introducing more advanced abstraction concepts.

  • Merging Interfaces

The most common and easiest type of declaration merging is merging interfaces. Take a look at the example below:

interface Footballer {
id: number;
}

interface Footballer {
email: string;
}

interface Footballer {
firstName: string;
lastName: string;
}

let footballer: Footballer = { id: 69, email: 'johndoe@yahoo.co.uk' firstName: 'John', lastName: 'Doe' };

In this example, each interface declared with the name Footballer is merged into one definition. This means the footballer object can have properties of all three interfaces. What about interface members with the same name? The non-functions should be of the same type - otherwise, the compiler will throw an error.

interface Footballer {
firstName: string;
lastName: string;
}

interface Footballer {
firstName: string; // that's ok
lastName: number; // error
}

Notice how function members with the same name describe an overload of the same function. In simple terms, the compiler chooses which function it should call based on the type of the provided argument. Worth noting is that the interfaces declared later take precedence over the earlier declared ones. You can find these interfaces below:

interface Footballer {
shoot(shot: CurveShot): CurveShot;
}

interface Footballer {
shoot(shot: FinesseShot): FinesseShot;
}

interface Footballer {
shoot(shot: FlairShot): FlairShot;
shoot(shot: ChipShot): ChipShot;
}

They will be merged in the following way:

interface Footballer {
shoot(shot: FlairShot): FlairShot;
shoot(shot: ChipShot): ChipShot;
shoot(shot: FinesseShot): FinesseShot;
shoot(shot: CurveShot): CurveShot;
}

However, there is an exception to this rule - signatures with string literal type parameters will always go to the top of the merged overload list.

TypeScript declaration merging

Typescript declaration merging applies also to namespaces. Check out the example below. 

namespace GameParticipants {
export class Striker {}
}

namespace GameParticipants {
export interface Footballer {
shotAccuracy: boolean;
}
export class Defender {}
}

The namespaces will be merged in the following way:

namespace GameParticipants {
export interface Footballer {
shotAccuracy: boolean;
}

export class Striker {}
export class Defender {}
}

All the exported interfaces and classes have been merged. However, members that were not exported aren’t visible inside the merged definition.

namespace Footballers {
let rightFooted = true;

export function isRightFooted() {
return rightFooted;
}
}

namespace Footballers {
export function isRightFooted() {
return rightFooted; // Error
}
}

Currently, in TypeScript you can also merge namespace with Classes, Functions and Enums.

It’s worth noting that declaration merging of namespaces between files is not supported. In fact, if you are using ES6 modules, it’s not recommended to use namespaces at all. Although, you can use module augmentation as a workaround. Check the example below.

// footballer.ts
export class Footballer {
// ...
}

// pass.ts
import { Footballer } from "./footballer";
declare module "./footballer" {
interface Footballer {
pass(passType: PassType);
}
}
Footballer.prototype.pass = function (f) {
// ...
}// game.ts
import { Footballer } from "./footballer";
import "./pass";
let footballer: Footballer;
footballer.pass(PassTypes.shortPass)

In the examples above, declarations were merged in the same way as if they had been declared in a single file. Keep in mind that with this solution, new top-level declarations cannot be declared in the augmentation. Also, it is not possible to augment default exports.

TS_declaration merging
  • Global Augmentation

If you need to add global scope declarations, you can also use the declare clause for module augmentation.


declare global {
interface Array<T> {
modifyArray(): Array<T>;
}
}

Array.prototype.modifyArray = function () {

};

Looking forward to exploring more TypeScript insights? Check out this article!

Ready to try declaration merging yourself?

Summing it up, declaration merging means merging two separate declarations declared with the same name into a single definition. It pays off to be aware of the fact that certain rules apply to that process, the summary of which we provided in this article.

Want to use TypeScript in practice? Join our friendly frontend team!
 

Navigate the changing IT landscape

Some highlighted content that we want to draw attention to to link to our other resources. It usually contains a link .