I can't access service property of Abstract class from Concrete class using super call, it just works with this. But when I use super, typescript doesn't show any type errors
interface Service {
do(): void;
}
abstract class Abstract {
constructor(protected readonly service: Service) {}
abstract execute(): void;
}
class Concrete extends Abstract {
constructor(service: Service) {
super(service);
}
execute(): void {
super.service.do(); // <- if i replace "super" with "this", it works
}
}
class ServiceImpl implements Service {
do(): void {
console.log("i saved something");
}
}
const concrete = new Concrete(new ServiceImpl());
console.log(concrete.execute()); // <- it will throw error
this code will throw
TypeError: Cannot read properties of undefined (reading 'do')
I tried calling service with super to improve my code readability, so others could easily identify service as a protected member of Abstract class. As Typescript recognised service from super call, by the type checker, i expected it would work, but it doesn't
The super
keyword lets you access properties of the prototype of the class. It does not access some hypothetical instance of the superclass (there is no such instance; the field from the superclass is overwritten by the field of the subclass). So if the class member you're referring to is bound to the class instance, then it will be undefined
when you access it on super
. If you want to access an instance field, you should use this
instead of super
.
This doesn't have much to do with protected
or abstract
classes or properties. You can indeed access methods or accessor properties (those using get
) with super
, since those do appear on the prototype:
class Parent {
constructor() { }
method() {
console.log("Parent");
}
get accessor() {
return "Parent";
}
prop = "Parent";
}
class Child extends Parent {
method() {
console.log("Child");
}
get accessor() {
return "Child";
}
prop = "Child;"
execute(): void {
this.method(); // "Child"
super.method(); // "Parent"
console.log(this.accessor); // "Child"
console.log(super.accessor); // "Parent"
console.log(this.prop); // "Child"
console.log(super.prop); // undefined
}
}
So then the real issue you're facing is that your code does not produce a TypeScript error, even though you are accessing an impossible super
field. This is apparently a known bug, as reported in microsoft/TypeScript#42214. It looks like TypeScript cannot determine that your service
is a field, because it is a parameter property, and thus does not complain. If you rewrite the code to not use parameter properties: you get the expected error:
abstract class Abstract {
protected readonly service: Service; // explicitly declare
constructor(service: Service) {
this.service = service; // assign
}
abstract execute(): void;
}
class Concrete extends Abstract {
constructor(service: Service) {
super(service);
}
execute(): void {
super.service.do(); // error!
}
}