Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Extending type when extending an ES6 class with a TypeScript decorator

I am trying to decorate a class with a decorator (a-la-angular style), and add methods and properties to it.

this is my example decorated class:

@decorator
class Person{

}

and this is the decorator:

const decorator = (target)=>{
    return class New_Class extends target {
        myProp:string
    }
}

but myProp is not a known property of Person:

person.myProp //Error - myProp does not exist on type Person

How can I decorate a typescript class and preserve type completion, type safety, etc ?

like image 429
rotemx Avatar asked Feb 26 '19 19:02

rotemx


People also ask

How do I extend a class in TypeScript?

Just like object-oriented languages such as Java and C#, TypeScript classes can be extended to create new classes with inheritance, using the keyword extends . In the above example, the Employee class extends the Person class using extends keyword.

How do I use TypeScript decorators?

Using Decorator Syntax In TypeScript, you can create decorators using the special syntax @expression , where expression is a function that will be called automatically during runtime with details about the target of the decorator. The target of a decorator depends on where you add them.

Which decorator is applicable for component class coded using TypeScript Mcq?

The parameter decorator is applied to the function for a class constructor or method declaration.

How do I create an instance of a class in TypeScript?

You can do this by using the new keyword, followed by a syntax similar to that of an arrow function, where the parameter list contains the parameters expected by the constructor and the return type is the class instance this constructor returns. The TypeScript compiler now will correctly compile your code.


2 Answers

I found a solution to implement kind-of multiple heritage (really cascades it) but it's worth taking a look.

Supose you have a class Base with a few properties and methods:

class Base {
    tableName = 'My table name';
    hello(name) {
     return `hello ${name}`;
    }
}

And you want a class to extend Base but you also have defined some properties you want to reuse. For that will do the following function:

type Constructor<T = {}> = new (...args: any[]) => T;
function UserFields<TBase extends Constructor>(Base: TBase) {
    return class extends Base {
        name: string;
        email: string;
    };
}

Now we can do a class that extends Base and extends UserFields and typescript language service will find properties from both classes. It emulates a multiple heritage but it's really a cascade.

class User extends UserFields(Base) { }
const u = new User();
u.tableName = 'users'; // ok
u.name = 'John'; // ok

By this way you could reuse the UserFields function with any other classes. A clear example of that is if you want to expose the User object on client side as a "clean" object and have the fields available and then you have a UserDb object with connections to database and any other server side methods can have the same fields too. We only define the database fields once!

Another good thing is you could chain mixins mixin1(mixin2....(Base)) to have as many attributes as you want to have in the same class.

Everybody is expecting the decorator attributes to be visible by typescript, but meanwhile is a good solution.

like image 80
Jeroni Avatar answered Sep 30 '22 15:09

Jeroni


To supplement jcalz response, going back to the definition of the Decorator Pattern, it does not change the interface/contracts of its target. It's not just terminology. TypeScript decorators share similarities with Java annotations and .NET attributes which are aligned with the fact not to change the interface: they just add metadata.

Class mixin is a good candidate to solve your question. But it's better not to use "decorator" in its name in order to avoid confusion.

like image 21
Romain Deneau Avatar answered Sep 30 '22 15:09

Romain Deneau