11
abstract class Route {
    abstract readonly name?: string;
    protected abstract pattern: string;

    public constructor() {
        // Do something with `this.name` and `this.pattern`.
        
        console.log(this.pattern); // Typecheck error
    }
    
    abstract handle(): void;
}

This throws an error because this.pattern is not to be accessed in the constructor. Why can't I access it?

soufiane yakoubi
  • 861
  • 11
  • 31
  • 3
    Because the derived class’ constructor won’t have been called, so the object may be in an invalid state. Some languages allow virtual-calls from a parent constructor but it’s still generally agreed to be a bad practice. TypeScript chose to disallow it. – Dai Sep 05 '20 at 21:42
  • 1
    The “solution” is to pass `pattern` as a parameter to `Route`’s constructor. If `pattern` cannot be determined by the subclass’ constructor prior to calling the parent constructor then you need to rethink your design. – Dai Sep 05 '20 at 21:43
  • @Dai, Oh yeah, right I'm just dumb. So, because the properties aren't implemented in the abstract class, when the `super()` call is executed in the subclass the properties aren't implemented anywhere yet. Please correct me if I'm wrong. – soufiane yakoubi Sep 05 '20 at 21:48
  • 1
    Pretty much (though I'm unsure exactly what you mean by "implemented"). – Dai Sep 05 '20 at 21:51
  • @Dai "assigned to the instance" would be more appropriate I think. Thank you for the answer. – soufiane yakoubi Sep 05 '20 at 21:55

1 Answers1

12

(Converting my comments to an answer)

Why can't I access it?

Because the derived class’ constructor won’t have been called, so the object may be in an invalid state. Some languages allow virtual-calls from a parent constructor but it’s still generally agreed to be a bad practice. TypeScript chose to disallow it.

This is mentioned in the documentation: https://www.typescriptlang.org/docs/handbook/classes.html

[...] each derived class that contains a constructor function must call super() which will execute the constructor of the base class. What’s more, before we ever access a property on this in a constructor body, we have to call super(). This is an important rule that TypeScript will enforce.

The solution in in case is to pass pattern as a parameter to Route's constructor. If pattern cannot be determined by the subclass’ constructor prior to calling the parent constructor then you need to rethink your design.

abstract class Route {
    
    constructor(
        private readonly pattern: string
    )
    {
        console.log( pattern );
    }
}

class Derived123 extends Route {
    
    constructor() {
        super( /*pattern:*/ "123" )
    }
}

class Derived456 extends Route {
    
    constructor() {
        super( /*pattern:*/ "456" )
    }
}
Dai
  • 141,631
  • 28
  • 261
  • 374