'How to access derived class members in abstract implementation of method?

How is it that Derived is perfectly happy to call this.log() but somehow doesn't know about this.value? I'd have thought value would be set before super() is called?

How can Derived have a value set by the constructor and still be accessed by this.log()?

abstract class Base
{
    protected abstract map() : void;    

    constructor()
    {
        this.map();
    }
}

class Derived extends Base 
{
    constructor(private value : string)
    {
        super();        
    }

    protected map(): void 
    {
        this.log();
    }

    log()
    {
        alert(this.value);
    }
}

const derived = new Derived("Hello world"); // Alerts "undefined"

Playground

EDIT: To clarify, I'm not asking how to implement an abstract method in general, I'm asking about how can I invoke the concrete method from the constructor of the abstract class that uses a value defined by the constructor of the concrete class without defining the constructor parameter(s) on the abstract class since every concrete instance needs a variety of different types and values which would make the constructor of the abstract class quite absurd.

I also would like to avoid having the consumer of the abstract class having to remember to call map(), and ideally, the instance of the concrete class should be valid once it's initialized.



Solution 1:[1]

@Fabrizio Gargiulo and @Shubham Wajo already mentioned the root problem which is from super() initialization before this.value assignment, but I'd give another way to fix it with a call stack trick.

Javascript only has a single thread, so all functions will proceed synchronously from top to bottom. super() is not an exception for it, but we can delay the execution with setTimeout for the call stack completed (all initializations are done).

abstract class Base
{
    protected abstract map() : void;    

    constructor()
    {
        //this will run after `Derived` initalization completed
        setTimeout(() => this.map())
    }
}

class Derived extends Base 
{
    constructor(private value : string)
    {
        super();        
    }

    protected map(): void 
    {
        this.log();
    }

    log()
    {
        alert(this.value);
    }
}

const derived = new Derived("Hello world"); //"Hello world"

Playground

Solution 2:[2]

This is because when you call super() the value property is not yet initialised and it is mandatory to call parent class's constructor which means you must call super() at the top of the constructor.

So as you are calling map method inside the Base class the value property is not yet initialised and thats why you are getting undefined.

Execute the following code and see the flow:

abstract class Base
{
    protected abstract map() : void;    

    constructor()
    {
        console.log("abstract base constructor")
        this.map();
    }
}

class Derived extends Base 
{
    constructor(private value : string)
    {
        super();
        console.log("value initialized",this.value)  
    }

    protected map(): void 
    {
        console.log("map function call")
        this.log();
    }

    log()
    {
        console.log("log functon call")
        alert(this.value);
    }
}

const derived = new Derived("Hello world"); // Alerts "undefined"

It's a good idea to call map method inside the Derived class constructor after calling super()

Playground

Solution 3:[3]

As far as I know, inside the Derived constructor, the first thing you can call is super(), only after you can initialize your Derived parameter "value". The problem is that you call this.map() before that value is ever initialized and for this reason the Alert shows undefined.

So you have two options in my opinion:

  1. You declare a protected property called "value" (or "_value") in Base class, and you init it before the this.map() statement.
  2. You declare and init such property in the Derived class, but, this.map() should be executed in Derived constructor.

Solution 1

abstract class Base
{
    protected abstract map() : void;    

    constructor(protected value : string)
    {
        this.map();
    }
}

class Derived extends Base 
{
    constructor(protected value : string)
    {
        super(value);    
    }

    protected map(): void 
    {
        this.log();
    }

    log()
    {
        alert(this.value);
    }
}

const derived = new Derived("Hello world"); // Alerts "undefined"

Solution 2

abstract class Base
{
    protected abstract map() : void;    

}

class Derived extends Base 
{
    constructor(private value : string)
    {
        super();              
        this.map();
    }

    protected map(): void 
    {
        this.log();
    }

    log()
    {
        alert(this.value);
    }
}

const derived = new Derived("Hello world"); // Hello world

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Nick Vu
Solution 2
Solution 3 Fabrizio Gargiulo