Katie
Katie

Reputation: 1538

Typescript: access static property of inherited class from base class constructor before calling super?

I have an abstract error class for service errors to inherit, to make error handling a bit easier. (ErrorBase is from https://gist.github.com/justmoon/15511f92e5216fa2624b#gistcomment-1928632)

export abstract class ServiceError extends ErrorBase {
  abstract statusCode(): number;
  abstract readonly errorCode: string;
  static readonly defaultMessage: string;
  constructor(readonly context?: any, message?: string) { super(message); }

  toJSON(key: any) {
    return {
      errorCode: this.errorCode,
      message: this.message,
      context: this.context
    };
  }    
}

Here's an example of a class which extends it:

export class ExampleError extends ServiceError {
  statusCode(): number { return 400; }
  errorCode: string = "err-example";
  static readonly defaultMessage = "This is an example error";
  constructor(context?: any, message?: string) {
    super(context, message ? message : ExampleError.defaultMessage);
  }
}

I've been trying to figure out a way to access the defaultMessage of the inherited class from inside of the base class's constructor so that I can simplify the constructors of my inherited classes. Is there any way I can do this?

Upvotes: 5

Views: 3368

Answers (2)

Coderer
Coderer

Reputation: 27264

Greg's answer is correct, you should be using this.constructor. You have to cast it as typeof ServiceError because of the reasons explained in this issue. It's pretty complex but the short version is that you can't say anything in the base class about the shape of the constructor function for subclasses -- Base could have constructor() while Sub has constructor(string, number) -- and that means that this.constructor has to type as Function until they can come up with a better idea.

It works because static assignments run from base class, then child class, before any constructors are invoked, then the subclass constructor is invoked but it must immediately call its parent constructor. All the parent's property assignments run before the constructor body, then the parent constructor runs, then we're back in the child constructor after the super() call.

If I understood your question correctly, this should do what you're asking for:

abstract class ServiceError {
  static defaultMessage = "Base default";
  constructor(message?: string) {
    if (!message) { message = (this.constructor as typeof ServiceError).defaultMessage; }
    console.log(message);
  }
}

class ExampleError extends ServiceError {
  static defaultMessage = "Sub default";
  constructor(message?: string) {
    super(message);
  }
}

let a = new ExampleError("Explicit"); // Logs "Explicit"
let b = new ExampleError(); // Logs "Sub default"

Playground version

Upvotes: 1

Greg Rozmarynowycz
Greg Rozmarynowycz

Reputation: 2085

Static properties are just properties defined on your prototype function. They compile like so:

// TypeScript
class A {
    public static property = 'my string';
}

// Compiled ES5 JavaScript
function A() {}
A.property = 'my string';

Therefore, you can use the constructor property on this to access the derived class/function that being used to construct the instance (or rather copied):

class A {
    public static defaultMessage = 'My Error A';
    constructor() {
        // will output "My Error B" if a instance of B is being constructed
        console.log(this.constructor['defaultMessage']);
    }
}

class B {
    public static defaultMessage = 'My Error B';
}

Upvotes: 4

Related Questions