I have an architecture like that and I want to extends my TemplateService
with a template but actually I have an error on it. Is there a way to extends an abstract class from a template ?
// Error message
ReferenceError: Service is not defined
abstract class TemplateService<Service> extends Service
^
// My code
abstract class TemplateService<Service> extends Service { // Cannot find name 'Service'.
constructor() {
const name = "Hello world !";
super(name);
}
public getJob(): string {
return "Job";
}
protected getAge(): number {
return 25;
}
}
class ImportedService {
constructor(private _name: string) {}
public getName() {
return this._name;
}
}
class MyService extends TemplateService<ImportedService> {
constructor() {
super();
}
public fooMethod(): void {
...
}
}
CodePudding user response:
It is a category error to write
abstract class TemplateService<Service> extends Service {}
TypeScript's static type system, including generic type parameters, is erased from the emitted JavaScript that actually runs. In the above, abstract
and <Service>
are part of the static type system, while the rest is valid JavaScript (at least for modern versions of JavaScript). Your code as written would be emitted to something like
class TemplateService extends Service {}
which hopefully shows the problem. Unless there happens to be a class constructor named Service
in scope, this would be a runtime error. You can't subclass a type; you need to subclass a constructor value.
TypeScript has some support for subclassing an arbitrary constructor value via the concept of mixins. Instead of writing TemplateService
as a class, you make it a class factory function that accepts a class constructor as an input. Conceptually it would look like
// don't write this, it's not valid TS
function TemplateService<C extends new (name: string) => object>(ctor: C) {
return class extends ctor { // error!
// ------> ~~~~~
constructor() {
const name = "Hello world !";
super(name);
}
public getJob(): string {
return "Job";
}
protected getAge(): number {
return 25;
}
};
}
And you'd use it like
class ImportedService {
constructor(private _name: string) { }
public getName() {
return this._name;
}
}
class MyService extends TemplateService(ImportedService) {
constructor() {
super();
}
public fooMethod(): void {
console.log(this.getName());
console.log(this.getJob());
console.log(this.getAge())
}
}
Unfortunately, TypeScript's mixin support assumes that you are going to just pass the constructor parameters through, so the above TemplateService
implementation gives compiler errors. There are open issues about this, such as microsoft/TypeScript#37142. For now all I could say is to work around it by using several type assertions to trick the compiler into allowing the mixin and into to giving it the correct type.
For the particular example code here, it could look like:
function TemplateService<C extends new (name: string) => object>(ctor: C) {
const c = class extends (ctor as new (...args: any) => object) {
constructor() {
const name = "Hello world !";
super(name);
}
public getJob(): string {
return "Job";
}
protected getAge(): number {
return 25;
}
};
return c as {
new(): InstanceType<C> & InstanceType<typeof c>;
}
}
Now the compiler believes that TemplateService(ImportedService)
is a no-arg constructor which produces instances of a combination (via intersection) of ImportedService
and the type of the anonymous class expression with a getJob()
method and protected getAge()
method.