TypeScript: 'super' must be called before before accessing 'this' in the constructor of a derived class

dotdotcommadot picture dotdotcommadot · May 30, 2016 · Viewed 16.8k times · Source

I've seen this question passing a few times before, but I think my question is more concerning an architectural approach of this.
In TypeScript it is not possible to use the this keyword before calling super (on a class that extends from another class).
But what if you need to do something as in the example below?
(Just for clarification: I'm creating a component lifecycle for a UI library, so it feels like I really need to do something like this, and I can't seem to think of any other way of tackling this).

Code

What I would like to do is this:

class Person 
{
    public firstName: string;

    constructor()
    {
        this.scream();
    }

    protected scream(): void
    {
        console.log(this.firstName);
    }
}

class Employee extends Person
{
    public lastName: string;

    constructor(firstName: string, lastName: string)
    {
        this.lastName = lastName;
        super(firstName);
    }

    protected scream(): void
    {
        console.log(this.firstName + ' ' + this.lastName);
    }
}

Problem

The constructor of the parent class, 'Person', calls a protected method.
The child class, 'Employee', wants to use its own parameter (this.lastName) when overriding this protected method.
But the code above is throwing the error (in Webstorm at least):
"'super' must be called before before accessing 'this' in the constructor of a derived class"

Possible Solution

A) Switch this.lastName = lastName with the supercall

class Employee extends Person
{
    ...

    constructor(firstName: string, lastName: string)
    {
        super(firstName);
        this.lastName = lastName;
    }

    ...
}

=> The problem here is that this.lastName will be undefined inside the scream() method on class 'Employee'.

B)
Use setTimeout(callback, 0). This way the this.scream() method will be called later.

class Person 
{
    ...

    constructor()
    {
        setTimeout(() => this.scream(), 0);
    }

    ...
}

=> But it just feels like a very ugly hack to me.

C)
Don't call this.scream()from inside the Person class, but call it from the consumer.

const employee: Employee = new Employee();
employee.scream();

=> But obviously this is not always what you want.

Question

  • Am I doing a dumb thing here?
  • Are there better ways to arrange my code so I don't need to do this?
  • Is there some way to work around this error?

Answer

dotdotcommadot picture dotdotcommadot · May 30, 2016

Another solution I eventually came up with, in addition to the ones provided by @iberbeu and @Nypan, is to add and intermediary initProps() method right before the call to scream():

class Person 
{
    public firstName: string;

    constructor(firstName: string, props?: any)
    {
        this.firstName = firstName;
        this.initProps(props);
        this.scream();
    }

    protected initProps(props: any): void
    {
    }

    protected scream(): void
    {
        console.log(this.firstName);
    }
}

class Employee extends Person
{
    public lastName: string;

    constructor(firstName: string, lastName: string)
    {
        super(firstName, {lastName});
    }

    protected initProps(props: any): void
    {
        this.lastName = props.lastName;
    }

    protected scream(): void
    {
        console.log(this.firstName + ' ' + this.lastName);
    }
}

Although I think both made a strong point and I should actually be using a factory pattern instead..