Before getting into module augmentation, let’s look at some TypeScript merging principles which will become useful as we progress.
In this post we talked about merging interfaces with interfaces. Additionally, we can merge interfaces with classes too. Let’s look at an example.
class Food {
cheese: string;
}
interface Food {
bacon: string;
}
const food = new Food();
food.bacon = "nice bacon";
food.cheese = "sweet cheese";
console.log(food); // {bacon: "nice bacon", cheese: "sweet cheese"}
In our example above, we can see that, the food
variable contains both bacon
and cheese
even though only cheese
was declared in the Food
class. This is because, the interface was merged with the class.
But what if our interface contains a method, for example, bake
class Food {
cheese: string;
}
interface Food {
bacon: string;
bake(item: string);
}
const food = new Food();
food.bake("cake"); // Error: food.bake is not a function
Though, the bake
method will be shown on the food
variable with the help of intelliSense, because the class Food
and the interface Food
will be merged, calling the bake
method will result in an error because interfaces contain only declaration but not implementations. To solve this, we can add the implementation of bake
to the Food
prototype.
Food.prototype.bake = (item) => console.log(item);
Now calling the bake
method will work.
food.bake("cake"); // cake
Module augmentation helps us to extend functionalities to third party libraries we may not have access to or classes in other files.
Say we have a Pet
class with a name
property and feed
method.
export class Pet {
name: string;
feed(feedType: string) {
console.log(feedType);
}
}
We then decide to import this class into our index.ts
file but instead of using only the methods and properties in the Pet
class, we want to add more functionalities. We can do that using module augmentation.
First, we import our Pet
class into our index.ts
file.
import { Pet } from "./pet";
./pet
is a module. In order to extend it, we have a declare a module using the same name and in that module, we will declare an interface with the same name as the class we are trying to extend. In the interface, we will include the properties and methods we want to add to the extended class.
declare module "./pet" {
interface Pet {
age: number;
walk(location: string);
}
}
TypeScript will merge both the Pet
class and the Pet
interface because they can be found in the same ./pet
module.
But that’s not all. Remember I explained that, interfaces don’t contain implementation for methods but only their declarations. For that reason, we will add the implementation of the walk
method to the prototype
of Pet
.
Pet.prototype.walk = (location:string) => `Likes to walk in the ${location}`
Now we can call both the methods and properties found in the Pet
class and the newly declared Pet
interface.
const pet = new Pet();
pet.name = "Looty";
pet.age = 3;
pet.feed("bacon"); // bacon
console.log(pet.name = "Looty"); // Looty
console.log(pet.age = 3); // 3
console.log(pet.walk("park")); // Likes to walk in the park
Now you may be wondering, instead of declaring an interface then adding the implementation of walk
method to the Pet
prototype, why didn’t we just declare a class with the same name so that when the class is initialized, we will have methods from both classes?
The answer is, TypeScript doesn’t allow merging between classes so we can’t create two or more classes with the same name. If you want to merge classes, there is a workaround using TypeScript mixins discussed about in this post or you can use a library I created just for that.
That’s it. Hope this was useful. 😎👌
Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.
While we believe that this content benefits our community, we have not yet thoroughly reviewed it. If you have any suggestions for improvements, please let us know by clicking the “report an issue“ button at the bottom of the tutorial.
This textbox defaults to using Markdown to format your answer.
You can type !ref in this text area to quickly search our full set of tutorials, documentation & marketplace offerings and insert the link!
Dear Alfred M. Adjei, Thank you for the great article!
But I have a question about node_modules class.
I want to augment class from node_modules and write like as your code. But tsc say: TS2339: Property ‘age’ does not exist on type ‘TransactionSettings’. Can you help me to augment external class ?
import { Ydb } from ‘ydb-sdk’;
declare module ‘ydb-sdk’ { // eslint-disable-next-line @typescript-eslint/no-namespace namespace Table { interface TransactionSettings { age: number; walk(location: string): void; } } }
// @ts-ignore Ydb.Table.TransactionSettings.prototype.walk = (location: string) =>
Likes to walk in the ${location}
;const a = new Ydb.Table.TransactionSettings({ serializableReadWrite: {} }); console.log(a);