'Property '' does not exist in type '' when using a class returned by a function in typescript

I have a function in TypeScript that returns a class. Then, the returned class is used to extend a new class. The purpose here is to switch the output class between two input classes depending on an external trigger and then export the class.

However, when I try to use the output class for instantiation or creating another class by extension, the type checker outputs an error indicating that the properties do not exist once I try to access the class properties.

Property 'name' does not exist in type 'class1'"

Nonetheless, the code runs and I get the expected output.

Instead, when I create a class extended from one of the original classes, there is no typechecker error. So the issue seems to be caused by the class returned by the function.

Here is a sample code (For clarity, this code replicates the reported issue but is not the original one. Just in order to keep confidentiality. In the final implementation, c1 and c2 can differ more, "type" variable is changing and "class1" is exported. Therefore I'm looking for a method to dynamically specify the class)

// CLASS client1
class c1 {
  name: string
  location: string = "paris"
  constructor(name: string){
    this.name = name
    this.output()
  }
  handle(){}
  process(): string {
    let value = this.output()
    return value
  }
  protected output(): string{
    return this.name+":"+this.location
  }
}

// CLASS client2
class c2 {
  name: string
  location: string = "tokyo"
  constructor(name: string){
    this.name = name
    this.output()
  }
  handle(){}
  process(): string {
    let value = this.output()
    return value
  }
  protected output(): string{
    return this.name+"->"+this.location
  }
}

// CLASS selector
let type = "c2"
function selector(){
  if (type == "c2"){
    return c2
  }
  else{
    return c1
  }
}

// CLASS作成
class class1 extends selector(){}


const member = new class1("jhon",1)
// (typechecker Error: Property 'name' does not exist in type 'class1'")
console.log(member.name)

// (typechecker Error: Property 'process' does not exist in type 'class1'")
class class2 extends class1{
  handle(){
    return this.process()
  }
}

const member2 = new class2("juan",1)
console.log(member2.handle())

By the way, in case of instantiation, I can solve the error by setting the interface.

interface base extends c1{}
const member = new class1("juan",1) as base

But for the case of creating another class with "extends", I cannot find how I should proceed to attach the interface.

Could I get any advice on how to solve this inconsistency after class return, or on to switch between two classes? I appreciate your help.

Here's a link to the error in TypeScript's playground



Solution 1:[1]

What you are doing is pretty confusing. You seem to be trying to dynamically specify what class to extend. However, there is only ever one class1, what you have does not dynamically create either c1 or c2 since that class statement is only parsed once.

I believe you are looking for a factory function:

class c1 {
  name: string
  location: string = "paris"
  constructor(name: string){
    this.name = name
    this.output()
  }
  handle(){}
  process(): string {
    let value = this.output()
    return value
  }
  protected output(): string{
    return this.name+":"+this.location
  }
}

// CLASS client2
class c2 {
  name: string
  location: string = "tokyo"
  constructor(name: string){
    this.name = name
    this.output()
  }
  handle(){}
  process(): string {
    let value = this.output()
    return value
  }
  protected output(): string{
    return this.name+"->"+this.location
  }
}

let type = "c2"

function createC(name: string) {
  if (type == "c2"){
    return new c2(name);
  }
  else{
    return new c1(name);
  }
}

const member = createC("john")

// Works
console.log(member.name);

The above is still a little iffy. It feels like you should have a base class for c1 and c2 and createC should always return the type of the base class. Having said that, because of TS inference and duck typing, the above code should work similarly to using a base class.

Without over-examining your example, the only difference between both classes is the location. Also note that you should avoid doing any work in constructors. Here's my suggestion.

abstract class c {
    abstract location: string;

    constructor(public name: string) {}

    protected output(): string {
        return `${this.name} ->  ${this.location}`;
    }

    process(): string {
        return this.output();
    }

}

class c1 extends c {
    location: string = "paris"
}

class c2 extends c {
    location: string = "tokyo";
}

let type = "c2"

function createC(name: string): c {
    return type === "c2" ? new c2(name): new c1(name);
}

const member1 = createC("jean");
console.log(member1.process());

type = "c1";
const member2 = createC("???");
console.log(member2.process());

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