Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

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?
like image 371
dotdotcommadot Avatar asked May 30 '16 10:05

dotdotcommadot


People also ask

What is super () in typescript?

The super keyword can be used in expressions to reference base class properties and the base class constructor. Super calls consist of the keyword super followed by an argument list enclosed in parentheses. Super calls are only permitted in constructors of derived classes.

What is super in constructor angular?

The super keyword is used to access properties on an object literal or class's [[Prototype]], or invoke a superclass's constructor. The super. prop and super[expr] expressions are valid in any method definition in both classes and object literals. The super(... args) expression is valid in class constructors.


2 Answers

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..

like image 130
dotdotcommadot Avatar answered Nov 01 '22 01:11

dotdotcommadot


Am I doing a dumb thing here?

Yes you are. As iberbeu said in his comment a constructor should never do anything that does not have to do with constructing the object. It is a case of bad practice that can lead to all sorts of unexpected behaviour.

Are there better ways to arrange my code so I don't need to do this?

Using the solution you provided in your C option is the way to go here.

Is there some way to work around this error?

It depends on what you actually want to do. The normal way of doing things is illustrated by yourself in your C option. If the problem you are having is related to actually instantiating complex objects you might want to look in to builder/factory patterns. But if you actually want the constructors to do something you are simply doing it wrong; constructors are not ment to perform actions, they are there to construct objects and nothing else.

like image 20
Nypan Avatar answered Nov 01 '22 01:11

Nypan