How to write a Kotlinesque extension function in Typescript?
Asked Answered
T

3

8

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?

Tolbert answered 9/2, 2021 at 20:8 Comment(2)
This is not possible in TypeScript which does not alter its emit based on type information.Tenpenny
improved my answer a bit, hope it helpsIntertidal
R
3

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.

Roof answered 10/2, 2021 at 19:53 Comment(3)
What is the best practice when I want to augment classes I have no control over in the Typescript way?Tolbert
For classes, see the first link or another example here stackoverflow.com/posts/38434418, but it isn't nearly as common as in Kotlin. I am not experienced enough in TypeScript to judge about best practice, but it seems to be fine.Roof
One difference that may or may not matter for your usecase is that augmentations are actually added to the prototype object, so e.g. Javascript clients will see them, and two "extension functions" with the same name on the same class (or subclass) will break.Roof
I
0

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() {
    ...
  }
}
Intertidal answered 9/2, 2021 at 20:21 Comment(13)
This is not an answer to my question. Extension functions are powerful because using them I can augment classes/interfaces I have no control over (like classes from dependencies). This is my use case.Tolbert
You can add a function to an interface, but not in the sense that a Kotlin extension does. In other words, you can add it to the type but not provide an implementation.Tenpenny
@AluanHaddad exactly, it describes typesIntertidal
I don't think you understand what I was getting at. Also, you make the false statement than interface cannot include functions. Interfaces describe the shapes of objects. Also, and JavaScript functions are data after the whole statement doesn't make any senseTenpenny
You didn't see that I edited my answer... should be pretty clear.Intertidal
I downvoted because the answer contains the line Interfaces in Typescript are meant to describe the shape of data, they cannot include functions; fix thatTenpenny
No, it isn't. Please read typescriptlang.org/docs/handbook/interfaces.html and see "You can also describe methods in an interface that are implemented in the class"Roof
@AlexeyRomanov my goal with this answer is not to regurgitate the docs... I can see at least 3 ways to have function/method types described in an interface. The point of the answer is showing they aren't callable in an interface. My answer already links to the docs, I think this is sufficient.Intertidal
But they are. Why do you think they aren't?Roof
If you have export interface Foo { update: () => void } you cannot call Foo.update() you can only implement a separate class that implements Foo and call update on the class. That's exactly what my answer explainsIntertidal
You cannot call Foo.update() because Foo is not a value; you can call foo.update() on any foo of type Foo.Roof
Let us continue this discussion in chat.Intertidal
Also, the type system is structural. Any object, be it an instance of a class or not implements an interface if it provides that interface's members. Even the implements clause is a mere formality as removing it does not change whether the class implements interface.Tenpenny
G
0

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.

Gona answered 12/7, 2023 at 10:16 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.