Search code examples
typescripttypescript-class

How to fix "Property has no initializer and is not definitely assigned in the constructor" error?


I am having a problem with these classes. I want to use the method doSomething() exclusive to class B without type casting it every time, but when I specify property a to be of type B, it tells me that it is not assigned in the constructor, which is somewhat false as the parent constructor does the assignment.

class A {

}

class B extends A {
  doSomething() { }
}

class One {
  constructor(protected a: A){  }
}

class Two extends One {
  protected a: B // Property 'a' has no initializer and is not definitely assigned in the constructor.

  constructor(){
    super(new B());
    // If I enter "this.a = new B();" here then the error disappears, but the code is redundant.
  }

  doStuff() {
    this.a.doSomething()
  }
}

What am I doing wrong?

Playground


Solution

  • The problem is that the proposal for adding class field declarations to JavaScript has different semantics from what you might expect and what TypeScript designers expected when they added them to TypeScript. It turns out that in JavaScript, class field declarations will be initialized via Object.defineProperty() and not by assignment, and all such declared fields without initializers will be initalized with undefined. So eventually you can expect code like yours to generate JavaScript that sets a to undefined in the subclass even though your intention is just to narrow the type from the base class. Blecch.

    So, in TypeScript 3.7, a --useDefineForClassFields flag was added, along with the declare property modifier. If you use --useDefineForClassFields, the compiler will output code that is compliant with the expected Object.defineProperty() semantics for class fields:

    And if you run your code as-is with that flag, you'll see the issue at runtime:

    new Two().doStuff()
    // [ERR]: "Executed JavaScript Failed:" 
    // [ERR]: this.a is undefined 
    

    The solution is to use the declare property modifier to narrow the subclass property without emitting any corresponding Object.defineProperty() code:

    class Two extends One {
      declare protected a: B // okay
    
      constructor() {
        super(new B());
      }
    
      doStuff() {
        this.a.doSomething()
      }
    }
    
    new Two().doStuff(); // okay now
    

    Playground link to code