Abstract class should not be required to implement all the properties/methods of the interfaces it implements. So the following should be legal:
interface A {
p;
m();
}
abstract class B implements A {
}
class C extends B {
get p() {...};
set p(v) {...};
m() {...}
}
But currently it gives:
Error:(2, 16) TS2420: Class 'B' incorrectly implements interface 'A'. Property 'p' is missing in type 'B'.
This issue is related to proposal #3578.
+1 for this use case. I was surprised it wasn't part of the original spec for abstract classes. Our use case is when making types for modules (such as sequelize) that use the definition object pattern to create objects from. With abstract classes + decorators we can define our types and our implementation details in one place, while creating the definition object for sequelize:
abstract class Model<T> implements Sequelize.Instance<T> {}
@model<GroupInstance>()
abstract class GroupInstance extends Model<GroupInstance> {
// Static methods
@classMethod
public static joinedByUser(): string {
return 'test';
}
// Table attributes
@attribute({
type: Sequelize.STRING,
allowNull: false,
validations: {
notNull: true,
notEmpty: true
}
})
public name: string;
@attribute({
type: Sequelize.BOOLEAN
})
public school: boolean;
// Instance methods
@instanceMethod
public hello(): string {
return `hi there from ${this.name}`
}
// Validations
@validation
private isValid() {}
}
But because we cannot extend the Instance interface without implementing it's members in the abstract class, we must use intersection types (not so pretty):
abstract class GroupInstance extends Model<GroupInstance> {
...
}
type GroupI = GroupInstance & Sequelize.Instance<GroupInstance>;
Although, the convenience of not writing the declaration would be nice, the possible confusion/complexity arising from this change would not warrant it. by examine the declaration, it is not clear which members appear on the type, is it all properties, methods, or properties with call signatures; would they be considered abstract? optional?
@mhegazy I used to think that abstract classes are just like interfaces but with some members implemented. I know that TypeScript very often works non-intuitively different from other languages, but that is the difference in this specific case comparing to languages that don't have such problems with abstract classes?
If you describe the problem with a concrete example I will appreciate this very much.
For a motivating example:
interface Clock {
enable(): void; // Specific to each implementer
disable(): void; // Specific to each implementer
setEnabled(value: boolean): void; // Common across implementers. Calls one of the above.
}
abstract class BaseClock implements Clock { // Complains that BaseClock doesn't implement all members of Clock. Of course it doesn't! It's abstract!
// Common implementation for all derived classes
setEnabled(value: boolean): void {
if (value) {
this.enable(); // If it didn't implement Clock, it couldn't call this anyway.
}
else {
this.disable();
}
}
}
Of course we can keep declaring setEnabled in each derived class as an instance property setEnabled: (value: boolean) => void;
and then add it to each prototype with a mixin, but it would've been nice for ABCs to do this automatically. It limits the usefulness of ABCs severely if they can only reference their own members.
And you can't work around this by adding stubs enable: () => void; disable: () => void;
to the ABC to satisfy the interface because the derived class then can't implement them as methods: error TS2425: Class 'BaseClock' defines instance member property 'enable', but extended class 'DerivedClock1' defines it as instance member function.
A workaround that continues to use ABCs is to implement everything in the ABC and duplicate everything that needs to be implemented in derived classes as abstract protected methods, which is ugly as hell.
interface Clock {
enable(): void;
disable(): void;
setEnabled(value: boolean): void;
}
abstract class BaseClock implements Clock {
enable(): void {
this._enable();
}
disable(): void {
this._disable();
}
setEnabled(value: boolean): void {
if (value) {
this._enable();
}
else {
this._disable();
}
}
protected abstract _enable(): void;
protected abstract _disable(): void;
}
class DerivedClock1 extends BaseClock {
protected _enable() {
console.log("enable");
}
protected _disable() {
console.log("enable");
}
}
Just ran into this, was very surprised by the behaviour. Why an already abstract class needs to redefine interface methods as abstract is beyond me.
I agree that this behavior is very unexpected.
Why an already abstract class needs to redefine interface methods as abstract is beyond me.
yes, this does not make sense
quite surprising behavior for me as well - don't think that it would introduce any risks, at least for scenarios that @mhegazy has mentioned. Actually, it could be much more easier for the code reader to dissect all the different methods and understand which particular behavior aspects where introduced either by interface or abstract class. And from the maintenance perspective, as @Arnavion has already mentioned here, it would be an additional work required to be done each time you'll change the interface - it would require additional changes in the abstract class which, in fact, should just introduce its behavior aspects which it is responsible for and, thus, it should not be touched each time the interface has changed.
@Arnavion what worked for me and is a little bit less ugly was to only declare the interface implementation on the concrete class.
interface Clock {
enable(): void;
disable(): void;
setEnabled(value: boolean): void;
}
abstract class BaseClock {
setEnabled(value: boolean): void {
// do something
}
}
class DerivedClock1 extends BaseClock implements Clock {
protected enable() {
console.log("enable");
}
protected disable() {
console.log("enable");
}
}
Still feels a bit weird
@piettes Your suggestion will not typecheck. BaseClock
functions cannot reference this.enable
or this.disable
(I assume you meant those rather than _
-prefixed versions) if this
is not itself a Clock
Oh you are right; it only "works" if the abstract class doesn't call functions from the interface.
Just small question, because this is for me probably most annoying feature of TypeScript - how much of you are using TS with PHP, thus you have a "compact" abstracts with the "heavy" ones on TS side?
I have one general usage patter:
interface IFoo {
enable(enable: boolean): IFoo;
proprietary(): IFoo;
}
abstract class AbstractFoo implements IFoo {
// ...code here
}
class FooBar extends AbstractFoo { // missing implements as it's enough to refer to abstract impl.
// ... cool stuff here
}
I would be really happy too to not be forced to reuse all methods from interfaces in abstracts.
I agree that this behavior is a bit unexpected. As an alternative, I've been getting around this using interface merging.
The code below results in a compile time error:
interface A {
x: any;
}
// ERROR: Class 'B' incorrectly implements interface 'A'.
// Property 'x' is missing in type 'B'.
export abstract class B implements A {
y: any;
}
I've been avoiding the compile time error by using something like this instead:
interface A {
x: any;
}
// Merge interface B with the abstract class declaration below.
export interface B extends A {}
// Note that the 'implements A' here is optional... you can remove it if you like.
export abstract class B implements A {
y: any;
}
+1
+1
Most helpful comment
Just ran into this, was very surprised by the behaviour. Why an already abstract class needs to redefine interface methods as abstract is beyond me.