paint-brush
Creating Extension Methods Using Typescriptby@msarica
17,312 reads
17,312 reads

Creating Extension Methods Using Typescript

by MehmetJanuary 8th, 2020
Read on Terminal Reader
Read this story w/o Javascript

Too Long; Didn't Read

Creating Extension Methods Using Typescript using decorators. C#'s extension method feature is a work around. We are adding the the function to the prototype of the passed in class. This trick will not work on most third party libraries assuming that they are safeguarded against prototype pollution. The beauty of extension methods is that it should work on classes that we don't have any control over. So this trick won't work on the most third-party libraries. (https://stackblitz.com/cms/TypeScript/issues/9)

Companies Mentioned

Mention Thumbnail
Mention Thumbnail
featured image - Creating Extension Methods Using Typescript
Mehmet HackerNoon profile picture

I really like C#'s extension method feature. I was trying to experiment it on Typescript with decorators.

export function extension(ctr: any ) {
    let originalFunction: Function;
    return function (target, propertyKey: string, descriptor: PropertyDescriptor) {
      originalFunction = descriptor.value;

      ctr.prototype[propertyKey] = function(...args){
        return originalFunction(this, ...args);
      }
    }
}

I created a decorator factory named

extension
. This takes the class that we want to add the method. Essentially what we want is to run this method on any object that is instance of "
ctr
".

Since Typescript doesn't give a build in extension method concept to us, as a work around, we are adding the the function to the prototype of the passed in class.

export class User {
  constructor(
    public name: string = 'joe',
    public lastname: string = 'doe'
  ){}
}

export class Extensions {
  @extension(User)
  static test(user: User){
    console.log(user.name , 'this works');
  }

  @extension(User)
  static gaveMoney(thisArg: User, amount: number, to: User){
    console.log(`${thisArg.name} gave ${to.name} \$${amount}`);
  }
}
const user = new User();
const user2 = new User('mehmet', '');

user.gaveMoney(10, user2);

user2.gaveMoney(20, user);
user.test();

As you can see that the methods that we decorated with

extension
are added to the prototype of
User
class.

If you are getting an error

Property 'gaveMoney' does not exist on type 'User'
you can do one of the following two things:

(user as any).gaveMoney(10, user2);

or

export interface User{
  test; // adding the name only will fix the error
  gaveMoney(n: number, u:User) // this will give intellisense
}

Problem

By mutating the prototype essentially we are polluting the prototype. So this trick will not work on

frozen
or
sealed
objects. Let's see the following as an example. We froze the
User2 
class and its prototype and got an error because our method wasn't able to modify the class.


export class Extensions2 {
  @extension(User2)
  static test(user: User2){
    console.log(user.name , 'this works');
  }
}

export class User2 {
  name = 'john'
}
Object.freeze(User2);
Object.freeze(User2.prototype);

const sealedUser = new User2();
sealedUser.test();


Conclusion

Although this trick will work for our stuff, probably it won't work on most third party libraries assuming that they are safeguarded against prototype pollution. The beauty of extension methods is that it should work on classes that we don't have any control over.

Let's hope that Typescript team decides to add this awesome feature. (https://github.com/microsoft/TypeScript/issues/9)

Demo for this article: https://stackblitz.com/edit/ts-ms-extension-methods-experiment?file=index.ts