You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
allow abstract interfaces in class definitions #5194
TypeScript 1.6 added support for abstract class classes which allow a class to have optional method definitions. I have a different situation where I am defining a TypeScript class. However, this TypeScript class is actually instantiated by some other 3rd party library which manually adds additional methods outside of TypeScript's knowledge. Additionally, this object created is also an HTMLElement. This situation would also come up if anyone manually adds a method to an object instance.
The problem is that in my class methods the this object is only known to have the methods I defined in my class. I could explicitly cast this as the base type when I need to use the 3rd party method or HTMLElement method like (<BaseInterface><any>this).base_method(), but this is ugly and not clear to users what interfaces are available, particularly further down the inheritance chain. Currently, the approach I am using to get proper type checking is to add an extra property just for casting:
class MyComponent {
get base(): BaseInterface { return <BaseInterface><any>this; }
get el(): HTMLElement { return <HTMLElement><any>this; }
}
This adds runtime overhead and is a bit ugly, as i have to reference base methods like instance.base.base_method(). Instead, I would like to do something like:
class MyComponent implements BaseInterface {
}
However, this doesn't work because I get an error that MyComponent doesn't implement base_method.
So, I am proposing to allow extends with multiple interfaces in addition to or instead of a single class:
class MyComponent extends BaseInterface, HTMLElement {
}
Or, if you want to be more explicit that we are trying to model something outside the normal TypeScript class hierarchy that is allowed by JavaScript, then perhaps requiring the user to specify that this is an abstract interface:
class MyComponent implements abstract BaseInterface, abstract HTMLElement {
}
The end result is that this would end up being considered an "intersection" type of MyComponent, BaseInterface, and HTMLElement.
Another pattern where this would be useful would be a class constructor that accepts an options object where the options are then used to extend the object instance itself.
interface Options {
required: string
optional?: string
}
class Model implements Options { // Error that required is missing in class definition
constructor(opt: Options) {
for (let k in opt) this[k] = opt[k];
}
method() {
console.log( this.optional ); // Error that optional does not exist for Model
}
}
Here we again get an error that "required" is missing from the Model implementation, even though it will be manually added in the constructor. Additionally, we can't even access optional options as members of the instance even though they are part of the interface definition that the class implements.
The following workaround works, but isn't very DRY and requires a redundant member specification:
interface Options {
required: string
optional?: string
}
class Model implements Options {
required: string // redundant type specifications
optional: string
constructor(opt: Options) {
for (let k in opt) this[k] = opt[k];
}
method() {
console.log( this.optional );
}
}
Leaving off the type specification in Model, but leaving the member name, does compile, but then we lose type checking for using that member via instances.
I am not sure i understand what you are trying to accomplish. can you elaborate..
Assuming that your class will get some properties/methods from the constructor, you want to different types, e.g.:
// things your user would be able to provide to you in the constructorinterfaceDynamicallyInitializedMembers{required: stringoptional?: string;}// Members on type Model that you want to declare but do not want to implement// and i would argue these should all be optional, otherwise define them in the classinterfaceModel{required: stringoptional?: string;}// Members on type Model that you to implement/initializeclassModel{member="hi";constructor(opt: DynamicallyInitializedMembers){for(letkinopt)this[k]=opt[k];}method(){console.log(this.required);console.log(this.member);if(this.optional){console.log(this.optional)}}}varm=newModel({required: "yes"});// access them all the samem.required;m.optional;m.member;m.method;
Now ideally you would write this as an extends clause in the interface Model but this is currently a bug, see #5347 , and when this is fixed, you should be able to write it as:
I don't understand exactly what you're trying to, but this now works:
interfaceBase{required: string;}interfaceMyClassextendsBase{}interfaceMyClassextendsHTMLElement{}classMyClass{method(): void{console.log(this.required);// you can access HTMLElement}}
From what you've said, I think you're trying to do something like this:
// @file: externalLibrary.tsclassExternalMyClassextendsMyClass{constructor(){// implementer must provide a value for required:super();this.required='foo';// maybe some HTMLElement setup here too?}}// @file: externalUser.tsletmine: MyClass=newExternalMyClass();// user can be certain that MyClass can be used as Base and HTMLElement:console.log(mine.required);
If so, I can confirm that all this compiles with #5359.
Activity
drarmstr commentedon Oct 12, 2015
Another pattern where this would be useful would be a class constructor that accepts an options object where the options are then used to extend the object instance itself.
Here we again get an error that "required" is missing from the Model implementation, even though it will be manually added in the constructor. Additionally, we can't even access optional options as members of the instance even though they are part of the interface definition that the class implements.
The following workaround works, but isn't very DRY and requires a redundant member specification:
Leaving off the type specification in Model, but leaving the member name, does compile, but then we lose type checking for using that member via instances.
mhegazy commentedon Oct 21, 2015
A feature that we enabled recently (#5290) is interface and class merging, so the last sample can be written as:
drarmstr commentedon Oct 21, 2015
Thanks, that is an improvement for some cases.
Though the following still seems to fail complaining that
methodis required for the constructor call:http://www.typescriptlang.org/Playground#src=declare%20interface%20Model%20%7B%0A%20%20%20%20required%3A%20string%0A%20%20%20%20optional%3F%3A%20string%0A%7D%0A%0Adeclare%20class%20Model%20%7B%0A%09constructor(opt%3A%20Model)%3B%0A%09method()%20%3Avoid%3B%0A%7D%0A%0Avar%20foo%20%3D%20new%20Model(%7B%20required%3A%20%22hi%22%20%7D)%3B
It would also fail for a data member as well:
Here the user can't specify the constructor because
memberis considered to be required for the interface as well.Anything in the works for the first pattern?
mhegazy commentedon Oct 21, 2015
I am not sure i understand what you are trying to accomplish. can you elaborate..
Assuming that your class will get some properties/methods from the constructor, you want to different types, e.g.:
Now ideally you would write this as an extends clause in the interface
Modelbut this is currently a bug, see #5347 , and when this is fixed, you should be able to write it as:sandersn commentedon Oct 21, 2015
I have a PR, #5359, which fixes the bug.
drarmstr commentedon Oct 22, 2015
Great, I can test this with your next release!
For the first pattern, will this fix allow merging classes with interfaces that also extend from
HTMLElement?sandersn commentedon Oct 22, 2015
I don't understand exactly what you're trying to, but this now works:
From what you've said, I think you're trying to do something like this:
If so, I can confirm that all this compiles with #5359.
drarmstr commentedon Oct 22, 2015
This is wonderful!
mhegazy commentedon Oct 23, 2015
Looks like the main issue is addressed with #5359. closing.