Extension methods in TypeScript

Extension methods in C# are a great way to modularise functionality within a class in such a way (for example) to have a basic object with minimal methods and then extend this with further functionality that can be reference only if required.

Hence you might only have a need for the bare bones class in some cases and therefore only need to include that in your code base, alternatively you might also depend upon some advanced functionality that can be added when required. Another use case is adding functionality to third party libraries to extend their functionality.

Ofcourse we might simple extend/inherit from the class to add our new functionality but this ultimately mean’s we’re using a different type. With extension methods in C# we extend a String (for example) with extra functionality as opposed to created a subclass named something else, like ExtendedString.

So can we implement something similar to C#’s extension methods in TypeScript (and therefore JavaScript)? Yes we can, let’s look at a simple example, an Option class for handling defined/undefined values in a functional way. Here’s the Option.ts file

export class Option {
    value: any;

    constructor(value: any) {
        this.value = value;
    }

    isSome(): boolean {
        return this.value != null;  
    } 

    isNone(): boolean {
        return !this.isSome();
    }
}

Hence this is a bare bones implementation which we might extend further with further functionality. Let’s create a file for our extension methods, mine’s named OptionExtensions.ts, which might look something like this

import { Option } from './Option';

declare module './Option' {
    interface Option {
        ifElse(elseValue: any): any;
    }
}

Option.prototype.ifElse = function(elseValue: any): any {
    return this.isSome() ? this.value : elseValue;
}

In the above code we declare a module with the same name as the one which wish to extend and then declare an interface with our new methods. The interface in essences merges with the type to allow us to view our new methods as if they’re on the original Option type.

Finally we can start implementing our new functionality via the JavaScript prototype functionality.

In this example you can see we also have access to the this reference to allow our new functionality to hook into the Option fields etc.

Now let’s look at this in usage

import { Option } from "./Option"
import "./OptionExtensions";

const o = new Option(undefined);
console.log(o.ifElse(999));

That’s it, we pull in the extension methods via the import “./OptionExtensions”; when we want the extended functionality and we can call our extension methods as if they were written into the Option class itself.