When trying to implement a fairly basic, but polymorphic, active record style model system we run into issues with the type system not respecting this
when used in conjunction with a constructor or template/generic.
I've posted before about this here, #5493, and #5492 appears to mention this behavior also.
And here is an SO post this that I made:
http://stackoverflow.com/questions/33443793/create-a-generic-factory-in-typescript-unsolved
I have recycled my example from #5493 into this ticket for further discussion. I wanted an open ticket representing the desire for such a thing and for discussion but the other two are closed.
Here is an example that outlines a model Factory
which produces models. If you want to customize the BaseModel
that comes back from the Factory
you should be able to override it. However this fails because this
cannot be used in a static member.
// Typically in a library
export interface InstanceConstructor<T extends BaseModel> {
new(fac: Factory<T>): T;
}
export class Factory<T extends BaseModel> {
constructor(private cls: InstanceConstructor<T>) {}
get() {
return new this.cls(this);
}
}
export class BaseModel {
// NOTE: Does not work....
constructor(private fac: Factory<this>) {}
refresh() {
// get returns a new instance, but it should be of
// type Model, not BaseModel.
return this.fac.get();
}
}
// Application Code
export class Model extends BaseModel {
do() {
return true;
}
}
// Kinda sucks that Factory cannot infer the "Model" type
let f = new Factory<Model>(Model);
let a = f.get();
// b is inferred as any here.
let b = a.refresh();
Maybe this issue is silly and there is an easy workaround. I welcome comments regarding how such a pattern can be achieved.
The size and shape of my boat is quite similar! Ahoy!
A factory method in a superclass returns new instances of its subclasses. The functionality of my code works but requires me to cast the return type:
class Parent {
public static deserialize(data: Object): any { ... create new instance ... }
// Can't return a this type from statics! ^^^ :(
}
class Child extends Parent { ... }
let data = { ... };
let aChild: Child = Child.deserialize(data);
// ^^^ Requires a cast as type cannot be inferred.
I ran into this issue today too!
A fixup solution is to pass the child type in as a generic extending the base class, which is a solution I apply for the time being:
class Parent {
static create<T extends Parent>(): T {
let t = new this();
return <T>t;
}
}
class Child extends Parent {
field: string;
}
let b = Child.create<Child>();
Is there a reason this issue was closed?
The fact that polymorphic this doesn't work on statics basically makes this feature DOA, in my opinion. I've to date never actually needed polymorphic this on instance members, yet I've needed every few weeks on statics, since the system of handling statics was finalized way back in the early days. I was overjoyed when this feature was announced, then subsequently let down when realizing it only works on instance members.
The use case is very basic and extremely common. Consider a simple factory method:
class Animal
{
static create(): this
{
return new this();
}
}
class Bunny extends Animal
{
hop()
{
}
}
Bunny.create().hop() // Type error!! Come on!!
At this point I've been either resorting to ugly casting or littering static create()
methods in each inheritor. Not having this feature seems like a fairly large completeness hole in the language.
@paul-go the issue is not closed... ?
@paul-go I've been frustrated with this issue also but the below is the most reliable workaround i've found. Each Animal subclass would need to call super.create() and just cast the result to it's type. Not a big deal and it's a one liner that can easily be removed with this is added.
The compiler, intellisense, and most importantly the bunny are all happy.
class Animal {
public static create<T extends Animal>(): T {
let TClass = this.constructor.prototype;
return <T>( new TClass() );
}
}
class Bunny extends Animal {
public static create(): Bunny {
return <Bunny>super.create();
}
public hop(): void {
console.log(" Hoppp!! :) ");
}
}
Bunny.create().hop();
\\
\\_ " See? I am now a happy Bunny! "
(') " Don't be so hostile! "
/ )= " :P "
o( )_
@RyanCavanaugh Oops ... for some reason I confused this with #5862 ... sorry for the battle axe aggression :-)
@Think7 Yep ... hence the "resorting to ugly casting or littering static create() methods in each inheritor". It's pretty hard though when you're a library developer and you can't really force end users to implement a bunch of typed static methods in the classes that they inherit from you.
lawl. Totally missed everything under your code :D
Meh was worth it, Got to draw a bunny.
:+1: bunny
:rabbit: :heart:
+1, would definitely like to see this
Have there been any discussion updates on this topic?
It remains on our enormous suggestion backlog.
Javascript already acts correctly in such a pattern. If TS could follow also that would save us from a lot of boilerplate/extra code. The "model pattern" is a pretty standard one, I'd expect TS to work as JS does on this.
I would also really like this feature for the same "CRUD Model" reasons as everyone else. I need it on static methods more than instance methods.
This would provide a neat solution to the problem described in #8164.
It's good that there're "solutions" with overrides and generics, but they aren't really solving anything here â the whole purpose of having this feature is to avoid such overrides / casting and create consistency with how this
return type is handled in instance methods.
I'm working on the typings for Sequelize 4.0 and it uses an approach where you subclass a Model
class. That Model class has countless static methods like findById()
etc. that of course do not return a Model
but your subclass, aka this
in the static context:
abstract class Model {
public static tableName: string;
public static findById(id: number): this { // error: a this type is only available in a non-static member of a class or interface
const rows = db.query(`SELECT * FROM ${this.tableName} WHERE id = ?`, [id]);
const instance = new this();
for (const column of Object.keys(rows[0])) {
instance[column] = rows[0][column];
}
return instance;
}
}
class User extends Model {
public static tableName = 'users';
public username: string;
}
const user = User.findById(1); // user instanceof User
This is not possible to type currently. Sequelize is _the_ ORM for Node and it is sad that it cannot be typed. Really need this feature. The only way is to cast everytime you call one of these functions or to override every single one of them, adapt the return type and do nothing but call super.method()
.
Also kind of related is that static members cannot reference generic type arguments - some of the methods take an object literal of attributes for the model that could be typed through a type argument, but they are only available to instance members.
đ° Can't believe this still isn't fixed / added.....
We could make a good use of this:
declare class NSObject {
init(): this;
static alloc(): this;
}
declare class UIButton extends NSObject {
}
let btn: UIButton = UIButton.alloc().init();
here is a use case that i wish worked (migrated from https://github.com/Microsoft/TypeScript/issues/9775 which i closed in favor of this)
Currently the parameters of a constructor cannot use this
type in them:
class C<T> {
constructor(
public transformParam: (self: this) => T // not works
){
}
public transformMethod(self: this) : T { // works
return undefined;
}
}
Expected: this to be available for constructor parameters.
A problem that is sought to be solved:
class TheirFluentApi {
totallyUnrelated(): TheirFluentApi {
return this;
}
}
class MyFluentApi<FluentApi> {
constructor(
public toNextApi: (self: this) => FluentApi // let's imagine it works
){
}
one(): FluentApi {
return this.toNextApi(this);
}
another(): FluentApi {
return this.toNextApi(this);
}
}
// self based fluent API;
const selfBased = new MyFluentApi(this => this);
selfBased.one().another();
// foreign based fluent API:
const foreignBased = new MyFluentApi(this => new TheirFluentApi());
foreignBased.one().totallyUnrelated();
Future-proof workaround:
class Foo {
foo() { }
static create<T extends Foo>(): Foo & T {
return new this() as Foo & T;
}
}
class Bar extends Foo {
bar() {}
}
class Baz extends Bar {
baz() {}
}
Baz.create<Baz>().foo()
Baz.create<Baz>().bar()
Baz.create<Baz>().baz()
This way, when TypeScript supported this
for static members, new user code will be Baz.create()
instead of Baz.create<Baz>()
while old user code will be work just fine. :smile:
This is really needed! especially for DAO that have static methods returning the instance. Most of them defined on a base DAO, like (save, get, update etc...)
I can say that it might cause some confusion, having this
type on a static method resolve to the class it's in and not the type of the class (i.e: typeof
).
In real JS, calling a static method on a type will result in this
inside the function being the class and not an instance of it... so it inconsistent.
However, in people intuition I think the first thing that pops up when seeing this
return type on a static method is the instance type...
@shlomiassaf It would not be inconsistent. When you specify a class as a return type for a function like User, the return type will be an instance of the user. Exactly the same, when you define a return type of this on a static method the return type will be an instance of this (an instance of the class). A method that returns the class itself can then be modeled with typeof this.
@felixfbecker this is totally a view point thing, this is how you choose to look at it.
Let's inspect what happens in JS, so we can infer logic:
class Example {
myFunc(): this {
return this;
}
static myFuncStatic(): this {
return this; // this === Example
}
}
new Example().myFunc() // instanceof Exapmle === true
Example.myFuncStatic() // === Example
Now, this
in real runtime is the bounded context of the function, this is exactly what happens in fluent api like interfaces and the polymorphic this feature help by returning the right type, which is just aligning with how JS works, A base class returning this
returns the instance which created by the derived class. The feature is actually a fix.
To sum up:
A method returning this
that is defined as part of the prototype (instance) is expected to return the instance.
Continuing with that logic, a method returning this
that is defined as part of the class type (prototype) is expected to return the bounded context, which is the class type.
Again, no bias, no opinion, simple facts.
Intuition wise, I will feel comfortable having this
returned from a static function represent the instance since it's defined within the type, but thats me. Others might think differently and we can't tell them their wrong.
The problem is that it needs to be possible to type both a static method that returns a class instance and a static method that returns the class itself (typeof this). Your logic makes sense from a JS perspective, but we are talking about return _types_ here, and using a class as a return type (here this) in TypeScript and any other language always means the instance of the class. To return the actual class, we have the typeof operator.
@felixfbecker This raises another issue!
If the type this
is the instance type, it's different than what the this
keyword refers to in the body of the static method. return this
yields a function return type of typeof this
, which is totally weird!
No, its not. When you define a method like
getUser(): User {
...
}
you expect to get a User _instance_ back, not the User class (that's what typeof
is for). It's how it works in every typed language. Type semantics are simply different from runtime semantics.
Why not use this
or static
keywords as a constructor in func for manipulating with a child class?
class Model {
static find():this[] {
return [new this("prop")]; // or new static(...)
}
}
class Entity extends Model {
constructor(public prop:string) {}
}
Entity.find().map(x => console.log(x.prop));
And if we compare this with an example in JS, we'll see what it works correctly:
class Model {
static find() {
return [new this]
}
}
class Entity extends Model {
constructor(prop) {
this.prop = prop;
}
}
Entity.find().map(x => console.log(x.prop))
You cannot use the this
type on a static method, I think that's the entire root of the issue.
@felixfbecker
Consider this:
class Greeter {
static getHandle(): this {
return this;
}
}
This type annotation is intuitive, but incorrect if the type this
in a static method is the class instance. The keyword this
has a type of typeof this
in a static method!
I do feel that this
_should_ refer to the instance type, not the class type, because we can get a reference to the class type from the instance type (typeof X
) but not vice-versa (instancetypeof X
?)
@xealot okay, why not use static
keyword instead this
? But this
in JS static context still points to a constructor.
@izatop yes, the generated javascript does work (properly or improperly). However, this is not about Javascript. The complaint isn't that the Javascript language doesn't allow this pattern, it's that Typescript's type system doesn't.
You cannot maintain type safety with this pattern because Typescript doesn't allow polymorphic this
types on static members. This includes class methods defined with the static
keyword and the constructor
.
@LPGhatguy but you did read my previous comment, right?! If you have a method with return type User
, you expect return new User();
, not return User;
. The same way a return value of this
should mean return new this();
in a static method. In non-static methods this is different, but it's clear because this
is always an object, you would not be able to instantiate it. But in the end, we basically all agree that this is the best syntax because of typeof
and that this feature is much needed in TS.
@xealot i understand what TypeScript doesn't allow polymorphic this
, howewer I'll ask you why not add this feature to TS?
I don't know if the following will work for all the use cases of this issue, but I have found out that using the this:
type in static methods in conjunction with smart use of the type inference system allows for fun typing. It might not be incredibly readable, but it does the job without having to redefine static methods in the children classes.
Works with [email protected]
Consider the following ;
// IModelClass is just here to describe an instanciator
// since we can't use typeof T (unfortunately) with
// the generic type system.
interface IModelClass<T extends Model> {
new (...a: any[]): T
// unfortunately, we have to put here again all the typing information
// of the static members (without static, since we are describing a class, not an instance)
some_member: string
create<T extends Model>(this: IModelClass<T>): T
}
class Model {
// Here we use this with the IModel<T> to force the
// type system to use T as our current caller.
static some_member: string
// When we call Dog.create() below, T is thus resolved
// to Dog *and stays that way*
// If typeof worked on generic types (it doesn't), we could have defined this method
// instead as
// static create<T extends Model>(this: typeof T): T { ... }
static create<T extends Model>(this: IModelClass<T>): T {
return new this() // whatever you fancy here
}
}
class Dog extends Model {
bark() { }
}
class Cat extends Model {
meow() { }
}
// Everything should be typed here, and we didn't have to redefine static methods
// in Dog nor Cat
let dog = Dog.create()
dog.bark()
let cat = Cat.create()
cat.meow()
@ceymard why is this
given as a parameter?
This is due to https://github.com/Microsoft/TypeScript/issues/3694 that was shipped with typescript 2.0.0 that allows defining a this parameter for functions (thus allowing for this
use in their body without problems and to also prevent incorrect use of the functions)
It turns out that we can use them on methods as well as static method, and that it allows for really funny stuff, such as using it to "help" the type inferer by forcing it to compare the this
we provide (IModelT
and uses it, thus correctly typing our function.
(Note that this
is a _special_ parameter understood by the typescript compiler, it does not add one more to the function)
I thought the syntax was something like <this extends Whatever>
. So this would still work if create()
has other parameters?
Absolutely
I also wanted to point out that this is important for build-in types.
When you subclass Array
for example, the from()
method of the subclass will return an instance of the subclass, not Array
.
class Task {}
class TaskList extends Array<Task> {
public execute() {}
}
// actually returns instance of TaskList at runtime
const tasks = TaskList.from([new Task()])
tasks.execute() // error, method execute does not exist on type Array
Any update on this issue? This feature would greatly enhance my team's experience with TypeScript.
Please note that this is rather critical since it's a commonly used pattern in ORMs and a lot of code behaves very differently than the compiler and type system think it should.
I would raise some counter arguments against counter argument.
As @shlomiassaf pointed out, allowing this
in static members will cause confusion because this
means different thing in static method and instance method. Furthermore, this
in type annotation and this
in method body have different semantics. We can avoid this confusion by introducing new keyword, but the cost of a new keyword is high.
And there is easy workaround in current TypeScript. @ceymard has already shown that. And I would prefer his approach because it captures the runtime semantic of static method. Consider this code.
class A {
constructor() {}
static create<T extends A>(this: {new (): T}) {} // constructor signature is exactly the same as A's
}
class B extends A {
constructor(a: number) {
super()
}
}
B.create() // correctly trigger compile error here
If polymorphic this is supported, compiler should report error in class B extends A
. But this is a breaking change.
@HerringtonDarkholme I don't see the confusion, return type of this
is exactly what I would expect from return new this()
. Potentionally we could use self
for that, but introducing another keyword (othet than the one used to produce the output) seems to be more confusing to me. We already use it as a return type, the only (but big) problem is it's not supported in static members.
The compiler should not require programmers to do workarounds, TypeScript is supposed to be an "enhanced JavaScript" and that's not true in this case, you have to do a complex workaround for your code to not generate millions of errors. It's nice that we can achieve it in current version somehow, but that doesn't mean it's fine and doesn't need a fix.
Why return type of this
is different from the type of this
in method body, then?
Why does this code fail? How can you explain it to a new comer?
class A {
static create(): this {
return this
}
}
Why would a superset of JavaScript fail to accept this?
class A {
static create() {
return new this()
}
}
abstract class B extends A {}
True, we might need to introduce another keyword then. Could be self
, or maybe instance
@HerringtonDarkholme Here is how I explain it to a newcomer.
When you do
class A {
static whatever(): B {
return new B();
}
}
the return type of B
means that you need to return an instance of B
. If you want to return the class itself, you need to use
class B {
static whatever(): typeof B {
return B;
}
}
Now, just like in JavaScript, this
in static members refers to the class. So if you use a return type of this
in a static method, you will have to return an instance of the class:
class A {
static whatever(): this {
return new this();
}
}
if you want to return the class itself, you need to use typeof this
:
class A {
static whatever(): typeof this {
return this;
}
}
that is exactly how types already work in TS since ever. If you type with a class, TS expects an instance. If you want to type for the class type itself, you need to use typeof
.
Then I would ask why this
does not have the same meaning in the method body. If this
in instance method's type annotation has the same meaning with this
in its' corresponding method body, why it's not the same for static method?
We are faced with a dilemma and there are three options:
this
means different things in different context (confusion)self
or static
I prefer the third approach because it reflects the runtime semantic of static method definition.
It also spots out errors on use site, rather than define site, that is, as in this comment, the error is reported in B.create
, not class B extends A
. Use site error is more precise in this case. (consider you declare a static method that reference to this
and then declare an abstract subclass).
And, most importantly, it does not require a language proposal for new feature.
Indeed preference differs from persons. But at least I want to see a more detailed proposal like this. There are a lot of issues to be solved like abstract class assignability, subclass incompatible constructor signatures and error report strategies.
@HerringtonDarkholme Because in the case of instance methods, runtime this
is an object instance and not a class, so there is no space for confusion. It is clear that the type this
is literally the runtime this
, there is no other option. While in the static case it behaves like any other class annotation in any object oriented programming language: annotate it with a class, and you expect an instance back. Plus in TS case, because in JavaScript classes are objects too, type it with typeof
a class and you get the literal class back.
I mean it would probably have been more consistent when back then when they implemented this
return type to also think about the static case and instead require users to always write typeof this
for instance methods. But that decision was made back then so we gotta work with it now, and as I said it doesn't really leave any room for confusion because this
in instance methods does not produce any other type (unlike classes, who have a static type and an instance type), so it is distinct in that case and will not confuse anyone.
OK, let's just assume no one will be confused by this
. How about abstract class assignability?
The staticness of methods is somewhat arbitrary. Any function can have properties and methods. If it can also be called with new
, we choose to call these properties and methods static
. Now this convention has been firmly cemented into ECMAScript.
@HerringtonDarkholme you are no doubt correct that the use of this
would cause confusion. However, since there is nothing incorrect about using this
I would say it is perfectly fine, especially when taking into account your astute cost benefit analysis of the various alternatives. I think this
is the least bad alternative.
I just tried to catch up on this thread from my original post, I feel like maybe this has gotten off track a little bit.
To clarify, the desire is for the this
type annotation to be polymorphic when used in the constructor, specifically as a generic/template. With respect to @shlomiassaf and @HerringtonDarkholme I believe the example with static
methods can be confusing and it is not the intent of this issue.
Even though it's not often thought of as such, the constructor of a class is static. The example (which I will repost with more clarifying comments) is not declaring this
on a static method, it is instead declaring this
for future use via a generic in the type annotation of a static method.
The distinction being that I don't want this
computed immediately on a static method, but in the future in a dynamic one.
// START LIBRARY CODE
// Constrains the constructor to one that creates things that extend from BaseModel
interface ModelConstructor<T extends BaseModel> {
new(fac: ModelAPI<T>): T;
}
class ModelAPI<T extends BaseModel> {
// skipping the use of a ModelConstructor in favor of typeof does not work
// constructor(private modelType: typeof T) {}
constructor(private modelType: ModelConstructor<T>) {}
create() {
return new this.modelType(this);
}
}
class BaseModel {
// This is where "polymorphic `this`" in static members matters. We are
// trying to say that the ModelAPI should create instances of whatever
// the *current* class is, not the BaseModel class. Much like it would
// at runtime.
constructor(private fac: ModelAPI<this>) {}
reload() {
// `reload()` returns a new instance of type Any, incorrect
return this.fac.create();
}
}
// END LIBRARY CODE
// START APPLICATION CODE
// Create a custom model class with custom behavior
class Model extends BaseModel {}
// Create an instance of the model API that produces my custom type
let api = new ModelAPI<Model>(Model); // ModalAPI should be able to infer "<Model>" from the constructor?
let modelInst = api.create(); // Returns type of Model, correct
let reset = modelInst.reload(); // Returns type of Any, incorrect
// END APPLICATION CODE
To anyone that thinks this is confusing, well, it's not super straight forward I agree. However, the use of this
in the BaseModel
's constructor isn't really a static use, it's a deferred use to be computed later. However, static methods (including the constructor) don't operate that way.
I think at runtime this all works as expected. However the type system is incapable of modeling this particular pattern. The inability for typescript to model a javascript pattern is the basis for why this issue is open.
Sorry if this is a confusing comment, written in haste.
@xealot I get your point, but other issues that specifically suggested polymorphic this
for static methods got closed as a duplicate of this one. I assume the fix in TS would enable both use cases.
What is your exact use case ? Maybe the ÂŽthis' solution is enough.
I use it in a custom ORM library with success
Le jeu. 20 oct. 2016 Ă 19:15, Tom Marius [email protected] a
Ă©crit :
Please note that this is rather critical since it's a commonly used
pattern in ORMs and a lot of code behaves very differently than the
compiler and type system think it should.â
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
https://github.com/Microsoft/TypeScript/issues/5863#issuecomment-255169194,
or mute the thread
https://github.com/notifications/unsubscribe-auth/AAtAoRHc45B386xdNhuCLB8iW6i82e7Uks5q16GzgaJpZM4GsmOH
.
Thanks to @ceymard and @HerringtonDarkholme.
static create<T extends A>(this: {new (): T}) { return new this(); }
did the trick for me đ
It is more obscure than static create(): this { return new this(); }
at first sight but at least, it is correct! It accurately captures the type of this
inside the static method.
I hope this somehow can be prioritized, even though it is not the most upvoted or commented etc. It's very annoying to have to use type argument to get the right type, for example:
let u = User.create<User>();
When it is practically possible to just do:
let u = User.create();
And it is inevitable that static sometimes refers to instance types.
I had another idea regarding the discussion about whether the this
type in static members should be the type of the static side of the class or the instance side. this
could be the static side, it would match what is done in instance members and what is the runtime behavior, and then you could get the instance type through this.prototype
:
abstract class Model {
static findAll(): Promise<this.prototype[]>
}
class User extends Model {}
const users: User[] = await User.findAll();
this would be in line with how it works in JavaScript. this
is the class object itself, and the instance is on this.prototype
. It is essentially the same mechanic as mapped types, equivalent to this['prototype']
.
In the same way you could imagine the static side to be available in instance members through this.constructor
(I can't think of a use case for this atm).
I also wanted to mention that none of the mentioned workarounds/hacks work for typing the ORM Model in Sequelize.
this
type annotation cannot be used in a constructor, so it doesn't work for this signature:ts
abstract class Model {
constructor(values: Partial<this.prototype>)
}
ts
abstract class Model {
static attributes: {
[K in keyof this.prototype]: {
type: DataType,
field: string,
unique: boolean,
primaryKey: boolean,
autoIncrement: boolean
}
};
}
+1 for this request. my ORM will be much clean.
Hope we'll see the clean solution soon
Meanwhile, thanks to all who offered solutions!
May we move further on this since there's no more discussion?
Where would you like to take it? This is still a way in which Typescript cannot cleanly model Javascript as far as I know and haven't seen a work around other than simply not doing it.
This is must-have feature for ORM developers, as we can see already three popular ORM owners asked for this feature.
@pleerock @xealot Solutions have been proposed above:
export type StaticThis<T> = { new (): T };
export class Base {
static create<T extends Base>(this: StaticThis<T>) {
const that = new this();
return that;
}
baseMethod() { }
}
export class Derived extends Base {
derivedMethod() { }
}
// works
Base.create().baseMethod();
Derived.create().baseMethod();
// works too
Derived.create().derivedMethod();
// does not work (normal)
Base.create().derivedMethod();
I'm using this extensively. The declarations of the static methods in base classes are a bit heavy but that's the price to pay to avoid distortion on the type of this
inside static methods.
@pleerock
I have an in-house ORM that uses the this:
pattern extensively without problems.
I think there is no need to overcharge the language when the feature is in fact already here albeit admittedly a little convoluted. The use-case for a clearer syntax is pretty limited imho and can introduce inconsistencies.
Maybe there could be a Stackoverflow thread with this question and solutions for reference ?
@bjouhier @ceymard I explained why all the workarounds in this thread don't work for Sequelize: https://github.com/Microsoft/TypeScript/issues/5863#issuecomment-269463313
And this is not just about ORMs, but also the standard library: https://github.com/Microsoft/TypeScript/issues/5863#issuecomment-244550725
@felixfbecker Just posted something about the constructor use case and deleted it (I realised that TS does not mandate constructors in every subclass just after posting).
@felixbecker Regarding the constructor case, I solve it by sticking to parameterless constructors and providing specialised static methods instead (create, clone, ...). More explicit and easier to type.
@bjouhier That would mean you have to redeclare basically every single method in every model subclass. Look how many there are in Sequelize: http://docs.sequelizejs.com/class/lib/model.js~Model.html
My argument is from a completely different perspective. While there are partial and somewhat unintuitive workarounds and we can argue and disagree on whether those are sufficient or not, what we can agree that this is an area where Typescript cannot easily model Javascript.
And my understanding is that Typescript should be an extension of Javascript.
@felixfbecker
That would mean you have to redeclare basically every single method in every model subclass.
Why? That's not my experience at all. Can you illustrate the problem with a code sample?
@bjouhier I illustrated the problem in depth with code samples in my comments in this thread, for a start https://github.com/Microsoft/TypeScript/issues/5863#issuecomment-222348054
But look at my create
example above. The create
method is a static method. It is not redeclared and yet it is properly typed in the subclass. Why would the Model
's methods need a redefinition then?
@felixfbecker Your _for a start_ example:
export type StaticThis<T> = { new (): T };
abstract class Model {
public static tableName: string;
public static findById<T extends Model>(this: StaticThis<T>, id: number): T {
const instance = new this();
// details omitted
return instance;
}
}
class User extends Model {
public static tableName = 'users';
public username: string;
}
const user = User.findById(1);
console.log(user.username);
@bjouhier Okay, so it does seem like the this: { new (): T }
annotations actually makes type inference work. Which makes me wonder why this is not the default type the compiler uses.
The workaround of course does not work for the constructor, since those cannot have a this
parameter.
Yes, it does not work for constructors, but you can get around the problem with a small API change, by adding a static create
method.
I understand that, but this is a JavaScript library, so we are talking about typing the existing API in a declaration.
If this
meant { new (): T }
inside a static method, then this
would not be the right type for this
inside the static method. For example new this()
would not be allowed. For consistency, this
must be the type of the constructor function, not the type of the class instance.
I get the problem with typing an existing library. If you don't have control over that library you can still create an intermediate base class (BaseModel extends Model
) with a properly typed create
function and derive all your models from BaseModel
.
If you want to access static properties of the base class you can use
public static findById<T extends Model>(this: (new () => T) & typeof Model, id: number): T {...}
But you probably have a valid point about the constructor. I don't see a hard reason why the compiler must reject the following:
constructor(values: Partial<this>) {}
I agree with @xealot that there is pain here. It would be so cool if we could write
static findById(id: number): instanceof this { ... }
instead of
static findById<T extends Model>(this: (new () => T), id: number): T { ... }
But we need a new operator in the typing system (instanceof
?). The following is invalid:
static findById(id: number): this { ... }
TS 2.4 broke these workarounds for me:
https://travis-ci.org/types/sequelize/builds/247636686
@sandersn @felixfbecker I think this is a valid bug. However, I cannot reproduce it in a minimal fashion. Callback parameter is contravariant.
// Hooks
User.afterFind((users: User[], options: FindOptions) => {
console.log('found');
});
Any chance this will be fixed at some point?
I ran into this today as well. Basically I wanted to build a singleton base class, like this:
abstract class Singleton<T> {
private static _instance?: T
public static function getInstance (): T {
return this._instance || (this._instance = new T())
}
}
Usage would be something like:
class Foo extends Singleton<Foo> {
bar () {
console.log('baz!')
}
}
Foo.getInstance().bar() // baz!
I tried about 10 variations of this, with the StaticThis
variant mentioned above, and many others. In the end there's only one version that would even compile, but then the result of getInstance()
was derived as just Object
.
I feel like this it's much much harder than it needs to be to work with construct as these.
Following works:
class Singleton {
private static _instance?: Singleton;
static getInstance<T extends Singleton>(this: { new(): T }) {
const constr = this as any as typeof Singleton; // hack
return (constr._instance || (constr._instance = new this())) as T;
}
}
class Foo extends Singleton {
foo () { console.log('foo!'); }
}
class Bar extends Singleton {
bar () { console.log('bar!');}
}
Foo.getInstance().foo();
Bar.getInstance().bar();
The this as any as typeof Singleton
part is ugly but it indicates that we are fooling the type system as _instance
must be stored on the constructor of the derived class.
Typically, the base class will be buried in your framework so it does not harm much if its implementation is a bit ugly. What matters is clean code in derived classes.
I was hoping in 2.8 nightlies to be able to do something like this:
static findById(id: number): InstanceType<this> { ... }
Sadly no such luck :(
@bjouhier code examples work like a charm. But break IntelliSense autocomplete.
React 16.3.0 was released and it seems impossible to properly type the new getDerivedStateFromProps
method, given a static method cannot reference class type parameters:
(simplified example)
class Component<P, S> {
static getDerivedStateFromProps?<K extends keyof S>(nextProps: P, prevState: S): Pick<S, K> | null
props: P
state: S
}
Is there any workaround? (I don't count "typing P and S as PP and SS and hope developers will properly make these two type families exactly match" as one :p)
PR: https://github.com/DefinitelyTyped/DefinitelyTyped/pull/24577
@cncolder I have witnessed the breaking of intellisense also. I believe it started with typescript 2.7.
One thing I don't see here, extending on the Singleton example - it would be a common pattern to make a Singleton / Multiton class have a protected constructor - and this cannot be extended if the type is { new(): T }
as this enforces a public constructor - the this
term or other solution provided here (instanceof this
, typeof this
etc) should resolve that problem. This is definitely possible with throwing an error in the constructor if the singleton didnUt request it's creation etc. but that defeats the purpose of typing errors - to have a runtime solution...
class Singleton {
private static _instance?: Singleton;
public static getInstance<T extends Singleton> ( this: { new(): T } ): T {
const ctor: typeof Singleton = this as any; // hack
return (ctor._instance || (ctor._instance = new this())) as T;
}
protected constructor ( ) { return; }
}
class A extends Singleton {
protected constructor ( ) {
super();
}
}
A.getInstance() // fails because constructor is not public
I don't know why you would need a singleton in TS with getInstance()
, you can just define an object literal. Or if you absolutely want to use classes (for private members), export only the class instance and not the class, or use a class expression (export new class ...
).
This is more useful for Multiton - and I believe I've found a pattern that works,... though it is still very hacky feeling without this type.. instead this is necessary
this
-> { new(): T } & typeof Multiton | Function
class Multiton {
private static _instances?: { [key: string]: any };
public static getInstance<T extends Multiton> (
this: { new(): T } & typeof Multiton | Function, key: string
): T {
const instances: { [key: string]: T } =
(this as typeof Multiton)._instances ||
((this as typeof Multiton)._instances = { });
return (instances[key] ||
(instances[key] = new (this as typeof Multiton)() as T)
);
}
protected constructor ( ) { return; }
}
class A extends Multiton {
public getA ( ): void { return; }
}
A.getInstance("some-key").getA();
assert(A.getInstance("some-key") === A.getInstance("some-key"))
new A(); // type error, protected constructor
I also have some code to store the key so it's accessible in the child class on creation, but I've stripped that out for simplicity...
Hi. I'm encountering a problem that needs polymorphic "this" for the constructor. Similar to this comment.
I want to create a class. The class itself has quite many properties so the constructor won't look good if all properties of that class are initialized via parameters of the constructor.
I prefer a constructor which accepts an object to initialize properties. For example:
class Vehicle {
// many properties here
// ...
constructor(value: Partial<Vehicle>) {
Object.assign(this, value)
}
}
let vehicle = new Vehicle({
// <-- IntelliSense works here
})
Then, I want to extend the class. For example:
class Car extends Vehicle {
// more properties here
// ...
}
let car = new Car({
// <-- I can't infer Car's properties here
})
It will be very nice if this
type can be used on the constructor too.
class Vehicle {
// ...
constructor(value: Partial<this>) {
// ...
So, IntelliSense can infer properties of the child class without additional boilerplate: For example, redefining the constructor. It will look more natural than using a static method for initializing the class.
let car = new Car({
// <-- Car's properties will be able to be inferred if Partial<this> is allowed
})
Thank you very much for your consideration.
I am glad I found this thread. I honestly think this is a very serious issue and was very discouraged to see it carry through to 3.0.
Looking at all the solutions and considering the various workarounds I played around with over the last couple of years, I come to the conclusion that any quick fix we try is simply going to fail if and when a solution is in place. And until then, every coercion we make simply requires too much manual work to maintain it across the project. And once published, you can't ask consumers to understand your own mindset and why you chose to fix something which is TypeScript if they simply consume your package in vanilla but like most of us, work with VSCode.
At the same time, writing code to make the Type System happy when the actual language is not the problem goes against the very same reason for using Typed features to begin with.
My recommendation to everyone is to accept that the squigglies are faulty and use //@ts-ignore because my code works as expected
when possible.
If your linter is immune to reasonable human intelligence, it is time to find one that knows its place.
UPDATE:
I wanted to add my own attempt to maybe-safely-augment static inference. This approach is strictly ambient as it is intended to be out-of-the-way. It is exhaustively explicit, forcing you to check your augmentations and not just assume that inheritance will do the work for you.
export class Sequence<T> {
static from(...values) {
// ⊠returns Sequence<T>
};
}
export class Peekable<T> extends Sequence<T> {
// no augmentations needed in actual class body
}
/// AMBIENT /// Usually keep those at the bottom of my files
export declare namespace Peekable {
export function from<T>(... values: T[]): Peekable<T>;
}
Obviously I cannot guarantee this pattern holds, nor that it would satisfy every scenario in this thread, but for now, it works as expected.
These choices stem from the concerning realization that until this point TypeScript's own lib files only include two class declarations!
/**
* Represents an Automation SAFEARRAY
*/
declare class SafeArray<T = any> {
private constructor();
private SafeArray_typekey: SafeArray<T>;
}
/**
* Automation date (VT_DATE)
*/
declare class VarDate {
private constructor();
private VarDate_typekey: VarDate;
}
Discussed for a while today. Key points:
this
refer to the instance side or the static side?InstanceTypeOf
but the reverse is not true. This also maintains symmetry that this
in the signature has the same type as this
in the body.new this()
in static
methodsthis
from the outside?Existing workarounds:
class Foo {
static boo<T extends typeof Foo>(this: T): InstanceType<T> {
return (new this()) as InstanceType<T>;
}
}
class Bar extends Foo {
}
// b: Bar
let b = Bar.boo();
Notably, this only works if Bar
's construct signature is properly substitutable for Foo
's
@RyanCavanaugh I've been trying to come up with an explanation where the value for this
comes from in your example static boo<T extends typeof Foo>(this: T): InstanceType<T>
, but I just don't get it. To be sure I re-read everything on generics, but still - nope. If I replacethis
with clazz
for instance, it won't work anymore and the compiler complains with "Expected 1 arguments, but got 0." So some magic's going on there. Could you explain? Or point me to the right direction in the documentation?
Edit:
Also, why is it extends typeof Foo
and not simply extends Foo
?
@creynders this
is a special fake parameter that can be typed for the context of the function. docs.
Using InstanceType<T>
doesn't work when there are generics involved.
My use case looks something like this:
const _data = Symbol('data');
class ModelBase<T> {
[_data]: Readonly<T>;
protected constructor(data: T) {
this[_data] = Object.freeze(data);
}
static create<T, V extends typeof ModelBase>(this: V, data : T): InstanceType<V> {
return new this(data);
}
}
interface IUserData {
id: number;
}
class User extends ModelBase<IUserData> {}
User.create({ id: 5 });
@mastermatt oh wow, totally did NOT pick that up when reading through the docs... Thanks
Looking at @RyanCavanaugh 's example, I was able to get our static methods working, but I also have instance methods that refer to the static methods, but I can't figure out how to get correct typing for that.
Example:
class Foo {
static boo<T extends typeof Foo>(this: T): InstanceType<T> {
return (new this()) as InstanceType<T>;
}
boo<T extends typeof Foo>(this: InstanceType<T>): InstanceType<T> {
return (this.constructor as T).boo();
}
}
class Bar extends Foo {
}
// b: Bar
let b = Bar.boo();
// c: Foo
let c = b.boo();
If I could find a workaround for this last issue I'd be set till something official comes along.
Also of note with that example, if the subclass tries to override any static method and then call super
, then it get's all upset as well... Other than these two issues, the workaround seems to be working alright. Although, those two issues are rather blocking for our code.
IIRC this is because this is not expressible in typescript without bending the type system a little. As in ; this.constructor is not at all guaranteed to be what you think it is or something like that.
In that precise case, I would have the boo()
instance method return this
and cheat a little inside, forcing the compiler to accept that I know what I'm doing.
My general reasoning is that I want the API to be as simple as possible for the rest of the code, even if at times it means cheating a little.
If someone has something more robust, I'd love it
class Foo {
static boo<T extends typeof Foo>(this: T): InstanceType<T> {
return (new this()) as InstanceType<T>;
}
boo(): this {
// @ts-ignore : wow this is ugly
return (this.constructor).boo();
}
}
class Bar extends Foo {
}
// b: Bar
let b = Bar.boo();
// c: Bar !
let c = b.boo();
You can also go the interface route and do something like that ;
interface FooMaker<T> {
new(...a: any[]): T
boo(): T
}
class Foo {
static boo<T extends typeof Foo>(this: T): InstanceType<T> {
return (new this()) as InstanceType<T>;
}
boo(): this {
return (this.constructor as FooMaker<this>).boo();
}
}
class Bar extends Foo {
}
// b: Bar
let b = Bar.boo();
// c: Bar !
let c = b.boo();
This is cheating too, but maybe a little more controlled ? I can't really say.
So the 1st, this
argument in the static "boo" method is treated specially, not as the regular argument? Any links to docs, describing this?
class Foo { static boo<T extends typeof Foo>(this: T): InstanceType<T> { return (new this()) as InstanceType<T>; } } class Bar extends Foo { } // b: Bar let b = Bar.boo();
I'm having the same (at least similar) problem.
I'm using Vanilla Javascript with JSDoc (not TypeScript), so I can't use/implement the workarounds revolving around generics proposed in this thread, but the root seems to be the same.
I've opened this issue: #28880
This issue have literally 3 years already.
Does anyone found a suitable workaround that could work for me?
Three years
@RyanCavanaugh's work around is great for static methods, but what about a static accessor?
class Factory<T> {
get(): T { ... }
}
class Base {
static factory<T extends Base>(this: Constructor<T>): Factory<T> {
//
}
// what about a getter?
static get factory<** no generics allowed for accessors **> ...
}
Three years
Okay, you're half decent at subtracting dates. But can you offer up a solution? ;)
@RyanCavanaugh This is a problem when we use this
value in generators as follows:
class C {
constructor(f: (this: this) => void) {
}
}
new C(function* () {
this;
yield;
});
When we make generators, we can't use arrow functions to use this
value. So now we have to declare this
type explicitly in subclasses. An actual case is as follows:
class Component extends Coroutine<void> implements El {
constructor() {
super(function* (this: Component) {
while (true) {
yield;
}
}, { size: Infinity });
}
private readonly dom = Shadow.section({
style: HTML.style(`ul { width: 100px; }`),
content: HTML.ul([
HTML.li(`item`)
] as const),
});
public readonly element = this.dom.element;
public get children() {
return this.dom.children.content.children;
}
public set children(children) {
this.dom.children.content.children = children;
}
}
https://github.com/falsandtru/typed-dom/blob/v0.0.134/test/integration/package.ts#L469
We don't have to declare this
type in subclasses if we can do it in base classes. However, this
value is not initialized when the generator is called synchronously in the super class. I avoided this problem in the code but this is a dirty hack. So this pattern may originally be mismatched with class-based programming languages.
Not the cleanest, but might serve as useful for accessing the child class from a static method.
class Base {
static foo<T extends typeof Base>() {
let ctr = Object.create(this.prototype as InstanceType<T>).constructor;
// ...
}
}
class C extends Base {
}
C.foo();
Though I am not sure whether the problem I am facing currently is due to this, but after a brief glance of this issue, it seems that is probably the case. Please correct if that is not the case.
So I have the following 2 classes related via inheritance.
export class Target {
public static create<T extends Target = Target>(that: Partial<T>): T {
const obj: T = Object.create(this.prototype);
this.mapObject(obj, that);
return obj;
}
public static mapObject<T extends Target = Target>(obj: T, that: Partial<T>) {
// works with "strictNullChecks": false
obj.prop1 = that.prop1;
obj.prop2 = that.prop2;
}
public prop1!: string;
constructor(public prop2: string) {}
}
export class SubTarget extends Target {
public subProp!: string;
}
Next, I add a mapObject
method in SubTarget
class as follows.
public static mapObject(obj: SubTarget, that: Partial<SubTarget>) {
super.mapObject(obj, that);
obj.subProp = that.subProp;
}
Though I expected this to work, I get the following error.
Class static side 'typeof SubTarget' incorrectly extends base class static side 'typeof Target'.
Types of property 'mapObject' are incompatible.
Type '(obj: SubTarget, that: Partial<SubTarget>) => void' is not assignable to type '<T extends Target>(obj: T, that: Partial<T>) => void'.
Types of parameters 'obj' and 'obj' are incompatible.
Type 'T' is not assignable to type 'SubTarget'.
Property 'subProp' is missing in type 'Target' but required in type 'SubTarget'.
Is this error generated due to this issue? Otherwise, an explanation would be really great.
Came here while looking for a solution to annotate static methods that return new instances of the actual class that are called on.
I think that the workaround suggested by @SMotaal (in combination with new this()
) is the cleanest and makes most sense to me. It allows to express the desired intent, doesn't force me to specify the type on every call to generic method and doesn't add any overhead in the final code.
But seriously, how is this not part of core Typescript yet? It's fairly common scenario.
@danielvy â I think that the disconnect between OOP and prototypes is the bigger mental gap that fails to make everyone happy. I think that the core assumptions about classes made way back before class features evolved in the spec did not line up, and breaking from that is a pitfall to many working TypeScript features, so everyone is "prioritizing" and that is okay but not for "types" in my view. Not having a solution that is better than the competition is only a problem if there is competition, that is purely why all eggs in one basket is always bad for everyone â I am being pragmatic and sincere here.
This doesn't work:
export class Base {
static getEntitySchema<T extends typeof Base>(
this: T,
): InstanceType<T> {
}
}
export class Extension extends Base {
static member: string = '';
static getEntitySchema<T extends typeof Extension>(
this: T,
): InstanceType<T> {
}
}
Type 'typeof Base' is missing the following properties from type 'typeof Extension ': member.
But shouldn't this be allowed since Extension extends Base?
Note this works:
export class Extension extends Base {
static member: string = '';
static getEntitySchema<T extends typeof Base>(
this: T,
): InstanceType<T> {
}
}
but then you can't use this.member inside it.
So I'm guessing from @ntucker post that the workaround only works one level deep unless the extended class matches the base class exactly?
Hello! What about protected constructors?
class A {
static create<T extends A>(
this: {new(): T}
) {
return new this();
}
protected constructor() {}
}
class B extends A {}
B.create(); // Error ts(2684)
If constructor is public - it's ok, but if not, it cause an error:
The 'this' context of type 'typeof B' is not assignable to method's 'this' of type '(new () => B) & typeof A'.
Type 'typeof B' is not assignable to type 'new () => B'.
Cannot assign a 'protected' constructor type to a 'public' constructor type.
Playground - http://tiny.cc/r74c9y
Is there any hope for this? Just stumbled upon this need once again :F
@ntucker Duuuuudee, you did it!!! The key realization for me was the making the static create method generic, declaring this
param, and then doing the InstanceType<U>
cast at the end.
From above ^
class Base<T> {
public static create<U extends typeof Base>(
this: U
) {
return new this() as InstanceType<U>
}
}
class Derived extends Base<Derived> {}
const d: Derived = Derived.create() // works đ
This perfectly satisfies my use case, and seems that as of TS 3.5.1 this satisfies the majority of ppl in this thread as well (see playground for exploration the static prop theAnswer
as well as 3rd level of extend)
Hot take: I think this works now and can be closed?
@jonjaques Btw you never use T. Also, this doesn't solve the problem I outlined which is overriding methods.
I feel like the mentioned solution was already suggested almost 4 years ago⊠And yet it's not something that solves the issue.
I opened a stackoverflow to see if there were other workarounds someone might know about and someone had a good summary of the problem I am experiencing:
"Generics used as method parameters, including this parameters, seem to be contravariant, but really you always want this parameters to be treated as covariant (or maybe even bivariant)."
So it seems like this is really something that is broken in TypeScript itself and should be fixed ('this' should be covariant or bivariant).
EDIT: This comment has a better way. https://github.com/microsoft/TypeScript/issues/5863#issuecomment-632509391
I don't have much to add, but this worked out well for me from https://github.com/microsoft/TypeScript/issues/5863#issuecomment-437217433
class Foo {
static create<T extends typeof Foo>(this: T): InstanceType<T> {
return (new this()) as InstanceType<T>;
}
}
class Bar extends Foo { }
// typeof b is Bar.
const b = Bar.create()
Hope this is useful to anyone that doesn't want to browse through this long issue.
Another use case is custom type guard member functions on non classes:
type Baz = {
type: "baz"
}
type Bar = {
type: "bar"
}
type Foo = (Baz|Bar)&{
isBar: () => this is Bar
}
The workaround almost gets me where I need to be, but I have one more wrench to throw in the gears. Take the following contrived example:
interface IAutomobileOptions {
make: string
}
interface ITruckOptions extends IAutomobileOptions {
bedLength: number
}
export class Automobile<O extends IAutomobileOptions> {
constructor(public options: O) {}
static create<T extends typeof Automobile, O extends IAutomobileOptions>(
this: T,
options: O
): InstanceType<T> {
return new this(options) as InstanceType<T>
}
}
export class Truck<O extends ITruckOptions> extends Automobile<O> {
constructor(truckOptions: O) {
super(truckOptions)
}
}
const car = Automobile.create({ make: 'Audi' })
const truck = Truck.create({ make: 'Ford', bedLength: 7 }) // TS Error on Truck
Here's the error:
The 'this' context of type 'typeof Truck' is not assignable to method's 'this' of type 'typeof Automobile'.
Types of parameters 'truckOptions' and 'options' are incompatible.
Type 'O' is not assignable to type 'ITruckOptions'.
Property 'bedLength' is missing in type 'IAutomobileOptions' but required in type 'ITruckOptions'.ts(2684)
This makes sense to me, as the create
method in the Automobile
class has no reference to ITruckOptions
through O
, but I'm wondering if there's a workaround for that issue?
Typically my extending class' options for the constructor will extend the base class' options interface, so I know they'll always include the parameters for the base class, but haven't got a reliable way of ensuring they include the parameters for the extending class.
I've also had to resort to just overriding methods in extending classes to inform them of the inheriting class' expected input and return types which just feels a bit smelly.
@Jack-Barry this works:
interface IAutomobileOptions {
make: string
}
interface ITruckOptions extends IAutomobileOptions {
bedLength: number
}
export class Automobile<O extends IAutomobileOptions> {
constructor(public options: O) { }
static create<T extends Automobile<O>, O extends IAutomobileOptions>(
this: { new(options: O): T; },
options: O
): T {
return new this(options)
}
}
export class Truck<O extends ITruckOptions> extends Automobile<O> {
constructor(truckOptions: O) {
super(truckOptions)
}
}
const car = Automobile.create({ make: 'Audi' });
const truck = Truck.create({ make: 'Ford', bedLength: 7 });
However, it's still not ideal since constructors are public so it's possible to just do new Automobile({ make: "Audi" })
.
@elderapo I think that gets me what I need - the example is of course contrived but I see where my lack of understanding of _Generics_ bit me a little. Thanks for clearing it up!
Hopefully this will help others, let me know if there's any room for improvement. It's not perfect, as the read functions could potentially become out of sync with the typings but it's the closest I could get to the pattern that we needed.
// Interface to ensure attributes that exist on all descendants
interface IBaseClassAttributes {
foo: string
}
// Type to provide inferred instance of class
type ThisClass<
Attributes extends IBaseClassAttributes,
InstanceType extends BaseClass<Attributes>
> = {
new (attributes: Attributes): InstanceType
}
// Constructor uses generic A to assign attributes on instances
class BaseClass<A extends IBaseClassAttributes = IBaseClassAttributes> {
constructor(public attributes: A) {}
// this returns an instance of type ThisClass
public static create<A extends IBaseClassAttributes, T extends BaseClass<A>>(
this: ThisClass<A, T>,
attributes: A
): T {
// Perform db creation actions here
return new this(attributes)
}
// Note that read function is a place where typechecking could fail you if db
// return value does not match
public static read<A extends IBaseClassAttributes>(id: string): A | null {
// Perform db retrieval here assign to variable
const dbReturnValue = {} as A | null
return dbReturnValue
}
}
interface IExtendingClassAttributes extends IBaseClassAttributes {
bar: number
}
// Extend the BaseClass with the extending attributes interface
class ExtendingClass extends BaseClass<IExtendingClassAttributes> {}
// BaseClass
const bc: BaseClass = BaseClass.create({ foo: '' })
const bca: IBaseClassAttributes = BaseClass.read('a') as IBaseClassAttributes
console.log(bc.attributes.foo)
console.log(bca.foo)
// ExtendingClass
// Note that the create and read methods do not have to be overriden,
// but typechecking still works as expected here
const ec: ExtendingClass = ExtendingClass.create({ foo: 'bar', bar: 0 })
const eca: IExtendingClassAttributes = ExtendingClass.read(
'a'
) as IExtendingClassAttributes
console.log(ec.attributes.foo)
console.log(ec.attributes.bar)
console.log(eca.foo)
console.log(eca.bar)
You could easily extend this pattern to the remaining CRUD actions.
In the example of @Jack-Barry, I try to get the read
function to return an instance of the class. I expected the following to work:
class BaseClass<A extends IBaseClassAttributes = IBaseClassAttributes> {
public static read<A extends IBaseClassAttributes, T extends BaseClass<A>>(
this: ThisClass<A, T>,
id: string,
): T | null {
// Perform db retrieval here assign to variable
const dbReturnValue = {} as A | null
if (dbReturnValue === null) {
return null;
}
return this.create(dbReturnValue);
}
}
but instead get the error Property 'create' does not exist on type 'ThisClass<A, T>'.
Would anyone have a workaround on how to tackle this?
@everhardt Since the create
method is static
it would need to be called on the class
of this
, not the instance. That said I don't really have a good workaround off the top of my head for how to access the class
function dynamically that still provides desired type checking.
The closest I can get is not particularly elegant, and is not able to use the existing type
from BaseClass.create
, so they could easily become out of sync:
return (this.constructor as unknown as { create: (attributes: A) => T }).create(dbReturnValue)
I have _not_ tested this.
@Jack-Barry then you get this.constructor.create is not a function
.
That does make sense: Typescript is interpreting this
as the instance, but for the eventual javascript parser, this
is the class as the function is static.
Maybe not the most inspired extension of Jack-Barry's example, but at this Playground Link you can see how I solved it.
The crux:
ThisClass
should describe all static properties and methods that methods of BaseClass (both static and on the instance) want to use with a polymorphic 'this'. (this.constructor as ThisClass<A, this>)
It's double work, as you have to define the types of static methods and properties on both the class and the ThisClass
type, but for me it works.
edit: fixed the playground link
PHP fixed this issue a long time ago. self. static. this.
Hey all, It's 2020, having _this_ issue. đ
class Animal {
static create() {
return new this()
}
}
class Bunny extends Animal {}
const bugs = Bunny.create() // const bugs: Animal
I would expect to have bugs
be an instance of Bunny
.
What's the current work-around? I've tried everything in this issue, nothing seems to fix.
Update: This worked, missed the this
argument definition.
class Animal {
static create<T extends typeof Animal>(this: T): InstanceType<T> {
return (new this()) as InstanceType<T>
}
}
class Bunny extends Animal {}
const bugs = Bunny.create() // const bugs: Bunny
The issue with that is that getters and setters canât have type parameters.
Itâs also too verbose.
I am dealing with a similar problem. To implement a "polymorphic inner class", the best I could find is the following:
class BaseClass {
static InnerClass = class BaseInnerClass {};
static createInnerClass<T extends typeof BaseClass>(this: T) {
return new this.InnerClass() as InstanceType<T['InnerClass']>;
}
}
class SubClass extends BaseClass {
static InnerClass = class SubInnerClass extends BaseClass.InnerClass {};
}
const baseInnerClass = BaseClass.createInnerClass(); // => BaseInnerClass
const subInnerClass = SubClass.createInnerClass(); // => SubInnerClass
It seems to work fine, but the typing of createInnerClass()
is far too verbose in my opinion, and I will have a lot of those in my codebase. Does anyone have any ideas on how to simplify this code?
The issue with that approach is that it doesnât work with static getters and setters.
How about implementing self.
for static properties ? I had this problem 4 years ago, and I'm coming back to that same thread with the same problem.
@sudomaxime Thatâs outside the scope of this issue and runs contrary to the TypeScript design goals for as long as ECMAScript doesnât natively support self.
as an alias for this.constructor.
in classes, which is unlikely to happen given that self
is a common variable name.
@abdullah-kasim's solution seems to work as described, but I am not able to get it to work with generic classes:
class Animal<A> {
public thing?: A;
static create<T extends typeof Animal>(this: T): InstanceType<T> {
return new this() as InstanceType<T>;
}
}
type Foo = {};
class Bunny extends Animal<Foo> {}
const bunny = Bunny.create(); // typeof bunny is Animal<unknown>
bunny.thing; // unknown :(
__ __
/_/| |\_\
|U|___|U|
| |
| , , |
( = Y = )
| ` |
/| |\
\| | | |/
(_|_|___|_|_)
'"' '"'
I'd appreciate any thoughts on if this is feasible as I've tinkered around with it for a bit with no luck.
@stevehanson Would this work for you?
class Animal<A> {
public thing?: A;
static create<T>(this: new () => T): T {
return new this() as T;
}
}
type Foo = {asd: 123};
class Bunny extends Animal<Foo> {
public hiya: string = "hi there"
}
const bunny = Bunny.create()
bunny.thing
const test = bunny.thing?.asd
const hiya = bunny.hiya
Tested this on typescript playground.
Inspired by this link:
https://selleo.com/til/posts/gll9bsvjcj-generic-with-class-as-argument-and-returning-instance
And I'm not sure how it managed to imply that T is the class itself. EDIT: Ah, I remembered, it managed to imply T because the silent this
parameter will always pass the class itself.
@abdullah-kasim this worked! Wow, thank you! For my specific case, my constructor takes an argument, so this was what it looked like for me, in case it helps anyone else:
static define<C, T, F = any, I = any>(
this: new (generator: GeneratorFn<T, F, I>) => C,
generator: GeneratorFn<T, F, I>,
): C {
return new this(generator);
}
Some of the solutions here don't work for static getters because I can't pass any arguments in:
Given the example below how can I move the default
static getter to a parent class?
class Letters {
alpha: string = 'alpha'
beta?: string
gamma?: string
static get default () {
return new this()
}
}
const x = Letters.default.alpha;
This may be something worth considering if we ever consider syntax improvements.
Possibly related: I'm having difficulty adding another layer of abstraction to @abdullah-kasim 's example. Essentially, I'd like to be able to turn Animal
into an interface, and allow Bunny
to define its own static create()
.
Here's a practical example (forgive me for abandoning the Bunny analogy!). I want to be able to define a few different document types, and each should be able to define a static factory method that turns a string into a document instance. I want to enforce that a document type must only define a parser for itself, and not for other document types.
(the types on the example below aren't quite right--hopefully it's clear enough what I'm trying to accomplish)
interface ParseableDoc {
parse<T>(this: new () => T, serialized:string): T|null;
}
interface Doc {
getMetadata():string;
}
// EXPECT: no error!
const MarkdownDoc:ParseableDoc = class implements Doc {
constructor(private meta:string){ };
getMetadata():string { return this.meta; };
static parse(serialized:string):typeof MarkdownDoc | null {
// do something specific
return null;
}
}
// EXPECT: type error, since class defines no static parse()
const MissingParseDoc:ParseableDoc = class implements Doc {
constructor(private meta:string){ };
getMetadata():string { return this.meta; };
}
// EXPECT: type error, since parse() should return a MismatchedDoc
const MismatchDoc: ParseableDoc = class implements Doc {
constructor(private meta:string){ };
getMetadata():string { return this.meta; };
static parse(serialized:string):typeof MismatchDoc | null {
// do something specific
return null;
}
}
(I think) I would like to be able to write something like this:
interface ParseableDoc {
getMetadata():string;
static parse<T extends ParseableDoc>(this: new () => T, serialized:string): T|null;
}
class MarkdownDoc implements ParseableDoc {
getMetadata():string { return ""; }
static parse(serialized:string):MarkdownDoc|null {
// do something specific
return null;
}
}
Any idea whether a workaround is possible here, too?
EDIT: Possible Workaround
Another use case on StackOverflow
For designing a generic lookup table class.
// Generic part
abstract class Table<T extends Model> {
instances: Map<number, T> = new Map();
}
abstract class Model {
constructor(
public readonly id: number,
public table: Table<this> // Error
) {
table.instances.set(id, this);
}
}
// Example: a table of Person objects
class Person extends Model {
constructor(
id: number,
table: Table<this>, // Error
public name: string
) {
super(id, table);
}
}
class PersonTable extends Table<Person> {}
const personTable = new PersonTable();
const person = new Person(0, personTable, 'John Doe');
// Note: the idea of using `this` as generic type argument is to guarantee
// that other models cannot be added to a table of persons, e.g. this would fail:
// class SomeModel extends Model { prop = 0; }
// const someModel = new SomeModel(1, person.table);
// ^^^^^^^^^^^^
@Think7 commented on May 29, 2016
đ° Can't believe this still isn't fixed / added.....
ha ha
2020 is here!
This is ridiculous and insane. It's 2020. 4 years later, why hasn't this been fixed?
Why not implement something like
class Model {
static create(){
return new static()
}
//or
static create(): this {
return new this()
}
}
class User extends Model {
//...
}
let user = new User.create() // type === User
Most helpful comment
Is there a reason this issue was closed?
The fact that polymorphic this doesn't work on statics basically makes this feature DOA, in my opinion. I've to date never actually needed polymorphic this on instance members, yet I've needed every few weeks on statics, since the system of handling statics was finalized way back in the early days. I was overjoyed when this feature was announced, then subsequently let down when realizing it only works on instance members.
The use case is very basic and extremely common. Consider a simple factory method:
At this point I've been either resorting to ugly casting or littering
static create()
methods in each inheritor. Not having this feature seems like a fairly large completeness hole in the language.