skip to Main Content

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

2

Answers


  1. In your case here:

    handleApplicantData<ApplicantModel>(applicantData: ApplicantModel): void {}
    

    ApplicantModel refers to the name of the generic not the type, as
    @Palladium02 mentions.

    What you want to do is this:

    handleApplicantData<T extends ApplicantModel>(applicantData: T): void {}
    

    But since that throws an error as well you would have to do this in the BaseApplicant class:

    abstract handleApplicantData<T extends ApplicantModel>(
      applicantData: T,
      isPrimaryApplicant: boolean
    ): void;
    

    Or if you want to keep it flexible:

    abstract handleApplicantData(
      applicantData: unknown,
      isPrimaryApplicant: boolean
    ): void;
    

    This would still allow you to declare the function as generic in the subclass.

    Final code would be:

    class ApplicantModel {
      name: string = '';
    }
    
    abstract class BaseApplicant {
      abstract handleApplicantData(applicantData: unknown, isPrimaryApplicant: boolean): void;
    }
    
    class Applicant extends BaseApplicant {
      handleApplicantData<T extends ApplicantModel>(applicantData: T): void {
        this.processApplicant(applicantData);
      }
    
      processApplicant(applicant: ApplicantModel): void {
        console.log(applicant.name);
      }
    }
    
    Login or Signup to reply.
  2. 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

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search