TypeScript Declaration Merging
Declaration merging is a unique feature in TypeScript where the compiler merges two or more separate declarations declared with the same name into a single definition. This concept is fundamental to how TypeScript models existing JavaScript libraries and allows for extensibility.
1. Merging Interfaces
The most common type of merging is interface merging. This allows you to add members to an interface declared elsewhere.
interface Box {
height: number;
width: number;
}
interface Box {
scale: number;
}
let box: Box = { height: 5, width: 6, scale: 10 };
Rules
- Non-function members must be unique. If they are not unique, they must have the same type. If they have different types, the compiler will issue an error.
- Function members (methods) are treated as overloads.
2. Merging Namespaces
Namespaces with the same name will merge their exported members.
namespace Animals {
export class Zebra { }
}
namespace Animals {
export interface Legged { numberOfLegs: number; }
export class Dog { }
}
// Result: Animals namespace has Zebra, Legged, and Dog.
3. Merging Namespaces with Classes, Functions, and Enums
This pattern allows you to describe inner classes or static properties using namespaces.
Class + Namespace
This is often used to add "static" inner classes or types to a class.
class Album {
label: Album.AlbumLabel;
}
namespace Album {
export class AlbumLabel { }
}
Function + Namespace
This allows you to add properties to a function (since functions are objects in JavaScript).
function buildLabel(name: string): string {
return buildLabel.prefix + name + buildLabel.suffix;
}
namespace buildLabel {
export let suffix = "";
export let prefix = "Hello, ";
}
console.log(buildLabel("Sam")); // "Hello, Sam"
Enum + Namespace
This allows you to add static methods to an enum.
enum Color {
Red = 1,
Green = 2,
Blue = 4
}
namespace Color {
export function mixColor(colorName: string) {
if (colorName == "Yellow") {
return Color.Red + Color.Green;
}
// ...
}
}
4. Module Augmentation
Although modules do not support merging in the same way (because they create their own scope), you can patch existing modules using declare module.
// observable.ts
export class Observable<T> {
// ... implementation ...
}
// map.ts
import { Observable } from "./observable";
declare module "./observable" {
interface Observable<T> {
map<U>(f: (x: T) => U): Observable<U>;
}
}
Observable.prototype.map = function(f) {
// ... implementation ...
}
5. Disallowed Merges
Not all merges are allowed. For example, you cannot merge classes with classes (use Mixins instead) or variables with variables.
programming/javascript/typescript/typescript programming/javascript/typescript/modules-and-namespaces