Search code examples
javascripttypescriptgenerics

This type parameter might need an `extends` constraint in typescript


Consider these classes and model in typescript.

class ApplicantModel {
  name: string = '';
}

abstract class BaseApplicant {
  abstract handleApplicantData<T>(applicantData: T, isPrimaryApplicant: boolean): void;
}

class Applicant extends BaseApplicant {
  handleApplicantData<ApplicantModel>(applicantData: ApplicantModel): void {
    // Error: This type parameter might need an `extends ApplicantModel` constraint.
    this.processApplicant(applicantData);
  }

  processApplicant(applicant: ApplicantModel): void {
    console.log(applicant.name);
  }
}

I'm getting error:

Argument of type 'ApplicantModel' is not assignable to parameter of type 'ApplicantModel'.(2345)
input.tsx(10, 23): This type parameter might need an `extends globalThis.ApplicantModel` constraint.

Why I need to use extends constraint. Why Argument of type 'ApplicantModel' is not assignable to parameter of type 'ApplicantModel'?

Playground


Solution

  • I'd say your generics are inappropriately scoped. If BaseApplicant is a specific class with a generic handleApplicantData method, then you're saying that callers of handleApplicantData can specify any type argument for T that they want. Meaning someone could call new Applicant().handleApplicantData(null) if they wanted. But you don't want or expect BaseApplicant or any of its subclasses to be that flexible.

    If you want implementers to specify the type argument, then you really want BaseApplicant to be a generic class with a specific handleApplicantData method. That is, move the generic from handleApplicantData to Baseapplicant:

    abstract class BaseApplicant<T> {
      abstract handleApplicantData(applicantData: T, isPrimaryApplicant: boolean): void;
    }
    

    Now instead of having your subclass try to make handleApplicantData generic somehow (which led to you trying and failing to specify the type argument as ApplicantModel, even though that was just the name of a new generic type parameter), your subclasses can choose what T is:

    class Applicant extends BaseApplicant<ApplicantModel> {
      handleApplicantData(applicantData: ApplicantModel): void {
        this.processApplicant(applicantData);
      }
    
      processApplicant(applicant: ApplicantModel): void {
        console.log(applicant.name);
      }
    }
    

    This compiles as desired. An Applicant can only handle ApplicantModel. Some other subclass of BaseApplicant could handle some other data.

    Playground link to code