Generic Abstract Classes in TypeScript

In my TypeScript code, I have an abstract generic class with a method that takes a parameter of a class type variable. When I tried to implement the abstract method in a derived class, I noticed that the TypeScript compiler doesn't check the type of the parameter in the derived method.

For example, I expected the compiler to catch an error at compile time in the process method of Class1 because the type of the parameter is incorrect.

Is this behavior intentional? Am I making a mistake in my code, or is it a bug in the TypeScript compiler?

class Product {
  id: number;
  name: string;
}

class Customer {
  id: number;
  name: string;
  address: string;
}

export abstract class BaseClass<TParam> {
  protected abstract process(param: TParam): void;
}

export class Class1 extends BaseClass<Customer> {
  protected process(param: Product): void {
    console.log(param);
  }
}

Answer №1

This isn't a flaw in the system.

When it comes to TypeScript, it utilizes a structural type system, which means that two object types are considered compatible if their properties align, regardless of whether the types have different names or originate from distinct classes/interfaces.

It's essential to understand that Customer can be assigned to Product, given that every instance of Customer includes a number-based id attribute and a string-based name attribute. The reverse is not true; assigning Product to Customer is not feasible because not all Product instances include the mandatory address property.

Is this considered an error? Should you be concerned that the compiler views a Customer as a specialized version of a Product? If so, the simplest solution would be to include a distinguishing property in each type for the compiler to differentiate between them. For example:

class Product {
  id!: number;
  name!: string;
  type?: "product" 
}

class Customer {
  id!: number;
  name!: string;
  address!: string;
  type?: "customer"
}

By doing so, the code will prompt an error when desired:

export abstract class BaseClass<TParam> {
  protected abstract process(param: TParam): void;
}

export class Class1 extends BaseClass<Customer> {
  protected process(param: Product): void { // error!
    //      ~~~~~~~ <-- Type 'Customer' is not assignable to type 'Product'.
    console.log(param);
  }
}

Alternatively, you may find it acceptable that the compiler sees a Customer as a specialized form of Product. In such cases, retaining your original types while examining why process() doesn't return a compiler error could prove enlightening:

export class Class1 extends BaseClass<Customer> {
  protected process(param: Product): void { // no error
    console.log(param);
  }
}

In this scenario, BaseClass<Customer> should possess a process() method capable of accepting a Customer. Nonetheless, process() actually accepts the broader Product type instead. Is this permissible? Absolutely! If process() functions correctly with any Product argument, then it most certainly handles any Customer argument (since a Customer falls under the category of a unique Product type, effectively allowing Class1 to extend BaseClass<Customer> successfully). This concept showcases how method arguments are contravariant; subclass methods are permitted to accept broader arguments than those on their supertype. TypeScript acknowledges this contravariance feature, hence the absence of any errors.

Conversely, employing covariant method arguments (where subclass methods demand more specific argument types compared to their superclass counterparts) isn't deemed entirely secure, but certain programming languages - including TypeScript - allow it to handle common scenarios. Essentially, TypeScript supports both contravariant and covariant method argument settings, also termed bivariant, despite lacking complete type safety. Consequently, if the roles were reversed, there still wouldn't be any issues:

export class Class2 extends BaseClass<Product> {
  protected process(param: Customer): void { // no error, bivariant
    console.log(param);
  }
}

To summarize: introducing properties to Customer and Product to establish structural independence or maintaining the current setup where Class1.process() compiles without any errors are valid choices. Either way, the compiler operates according to its intended design.

Hopefully, this clarifies matters for you. Best of luck!

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Unable to access the redux store directly outside of the component

When trying to access my store from a classic function outside the component, I encountered an error while calling getState(): Property 'getState' does not exist on type '(initialState: any) => any' Below is the declaration and im ...

Tips for efficiently saving data using await in Mongoose

Currently, the code above is functional, but I am interested in utilizing only async/await for better readability. So, my query is: How can I convert cat.save().then(() => console.log('Saved in db')); to utilize await instead? The purpose of ...

Enhance the API response for Angular service purposes

I am currently working on modifying the response returned by an API request. At the moment, I receive the response as: [ { name: "Afghanistan" }, { name: "Åland Islands" } ] My goal is to adjust it to: [ { name: "A ...

The request to search for "aq" on localhost at port 8100 using Ionic 2 resulted in a 404 error, indicating that the

Trying to create a basic app that utilizes an http request, but facing challenges with cors in ionic 2. To begin with, modifications were made to the ionic.config.json { "name": "weatherapp", "app_id": "", "v2": true, "typescript": true, "prox ...

Exploring the concept of rest arrays within a destructured object

Is there a way to declare c as an optional array of any type in this code snippet? const a = ({ b, ...c }: { b: string, c: ? }) => null ...

Struggling to locate components in your React, Next.JS, and Typescript project? Storybook is having trouble finding them

I have set up Storybook with my Next.js, TypeScript, and React project. The main project renders fine, but Storybook is breaking and giving me the error message: "Module not found: Error: Can't resolve 'components/atoms' in...". It appears t ...

Combine the object with TypeScript

Within my Angular application, the data is structured as follows: forEachArrayOne = [ { id: 1, name: "userOne" }, { id: 2, name: "userTwo" }, { id: 3, name: "userThree" } ] forEachArrayTwo = [ { id: 1, name: "userFour" }, { id: ...

Using Typescript with Vue.js: Defining string array type for @Prop

How can I properly set the type attribute of the @Prop decorator to be Array<string>? Is it feasible? I can only seem to set it as Array without including string as shown below: <script lang="ts"> import { Component, Prop, Vue } from ...

The Network plugin is having issues with the PWA application in Ionic 4

I've been utilizing the network plugin successfully on native/Cordova devices. However, I have encountered an issue when trying to use it on a PWA app (specifically when there is no wifi connection). Can anyone shed light on why this might be happenin ...

Potential uncertainty in Angular FormControl email validation due to potential null Object

Whenever I run the command ng s --aot, I encounter this message: Object is possibly 'null'. I've been trying various solutions all morning to resolve it, but without success. The issue seems to be related to objects like email.valid, dirty, ...

Looking for a way to detect changes in a select menu using Angular?

How can I determine with the openedChange event if there have been any changes to the select box items when the mat select panel is closed or opened? Currently, I am only able to detect if the panel is open or closed. I would like to be able to detect any ...

What method can be utilized to selectively specify the data type to be used in TypeScript?

Currently, I am facing a scenario where a certain value can potentially return either a string or an object. The structure of the interface is outlined as follows: interface RoutesType { projects: string | { all: string; favorite: string; cr ...

On which platform is the getFeatureInfo request constructed using Cesium?

Currently, I am working with Cesium and Angular. I am trying to locate where the request URL is generated for GetFeatureInfo in Cesium, but unfortunately I am unable to find it. My goal is to display feature information when clicking on the map. However, ...

Set up a new user account in Angular 5 Firebase by providing an email address and password

My goal is to create a new user with an email, password, and additional data such as their name. This is how my user interface looks: export interface UserInterface { id?: string; name: string; email: string; password: string; status: string ...

Is there a way for me to navigate from one child view to another by clicking on [routerLink]?

Currently, I am facing an issue with switching views on my Angular website. The problem arises when I attempt to navigate from one child view to another within the application. Clicking on a button with the routerlink successfully takes me to the new view, ...

Issues with NPM start arise moments after incorporating create react app typescript into the project

I've encountered an error while trying to initiate my create react app with Typescript. Despite following all the necessary steps, including adding the .env file (with SKIP_PREFLIGHT_CHECK=true) and updating/reinstalling NPM, I keep facing this issue. ...

Angular 15 experiences trouble with child components sending strings to parent components

I am facing a challenge with my child component (filters.component.ts) as I attempt to emit a string to the parent component. Previously, I successfully achieved this with another component, but Angular seems to be hesitant when implementing an *ngFor loop ...

In TypeScript, both 'module' and 'define' are nowhere to be found

When I transpile my TypeScript using "-m umd" for a project that includes server, client, and shared code, I encounter an issue where the client-side code does not work in the browser. Strangely, no errors are displayed in the browser console, and breakpoi ...

Utilizing TypeScript to Define Object Properties with String Keys and Values within Parentheses

I am in the process of developing a telegram bot I have the need to save all my messages as constants My message schema is structured as follows: type MessagesSchema = { [K in keyof typeof MessagesEnum]: string } Here is an example implementatio ...

Refreshing the cache in SWR, but the user interface remains unchanged inexplicably - SWR hook in Next.js with TypeScript

I am currently working on a project that resembles Facebook, and I am facing an issue with the like button functionality. Whenever I press the like button, I expect to see the change immediately, but unfortunately, SWR only updates after a delay of 4-8 sec ...