How to write a Kotlinesque extension function in Typescript?

556 views Asked by At

I Kotlin if I have an interface like this:

interface User {
    val name: String
    val email: String
}

I can write an extension function like this anywhere in the code:

fun User.toUserDto(): UserDto {
    TODO()
}

In Typescript if I have a similar interface:

export default interface User {
    name: string;
    email: string;
}

How can I augment it in a similar way? Is this a best practice in the language? Is there an alternative to this I don't know about?

3

There are 3 answers

13
inorganik On

Interfaces in Typescript can only describe the shape of data, you cannot make instances of them or mutate them.

To write an extension function of an interface, you would need to implement the interface in a class, and add whatever you need on the class.

export class SomeUserClass implements User {
  name: string;
  email: string;
  
  constructor(name, email) {
    ...
  }

  toUserDto() {
    ...
  }
}
3
Alexey Romanov On

You can augment classes like this, but it depends on the prototype property and there's no User.prototype (and no User value at all either).

You can also see this very long discussion. The explanation why the Kotlin/C# approach was rejected is here.

0
HeikoG On

Assuming your User interface is coming from a third party or is automatically generated, you have to to do 4 things:

  1. Define your desired interface

    export interface UserExtensions {
       toUserDto(): UserDTO
    }
    
  2. Tell the compiler that the generated/original interface provides the function (merge interfaces)

    declare module "./generated" { 
        interface User extends UserExtensions {}
    }
    
  3. Tell the compiler that underlying class provides the function (merge interface with class)

    declare module "./UserClassImpl" { 
        interface UserClass extends UserExtensions {}
    }
    
  4. Augment/Extend the class

    UserClass.prototype.toUserDto = (): UserDTO {
        return createUserDto(this)
    }
    

If there is no underlying class, you are out of luck. In case your data comes through axios, you could exchange the underlying anonymous json object with the true class through transformResponse.