What is the reason for instances being compatible even if their class constructors do not match?

Why are the constructors in the example below not compatible, but their instances are?

class Individual {
  name: string;
  age: number;
  constructor(name: string, age: number) {
    this.name = name;
    this.age = age;
  }
}

class Worker {
  name: string;
  age: number;
  salary: number;
  constructor(name: string, age: number, salary: number) {
    this.name = name;
    this.age = age;
    this.salary = salary;
  }
}

const indCtor: typeof Individual = Worker; // an error is thrown as constructors are incompatible
const individual: Individual = new Worker("", 0, 0); // however, the instances can still be compatible - how does this work?

Answer №1

Take a look at microsoft/TypeScript#3841 for a definitive response to this inquiry. The constructor property of class instances lacks strong typing in TypeScript. It is simply assigned the type Function, allowing types like Employee and Person to be interchanged without conflict.


However, why isn't p.constructor more strongly typed? Refer to this comment in microsoft/TypeScript#3841 for explanation.

In TypeScript, it is highly preferable for extended classes to also extend their instances. This allows class Foo extends Bar { ⋯ } to imply that Foo extends Bar, ensuring that class hierarchies align with type hierarchies. Without this alignment, code like const bar: Bar = new Foo() would throw errors, causing major disruption to existing codebases.

JavaScript permits subclass constructors to differ from those of their superclasses, which is necessary given some JavaScript inheritance patterns. Thus, scenarios like the following are allowed:

class Person {
  name: string;
  age: number;
  constructor(name: string, age: number) {
    this.name = name;
    this.age = age;
  }
}

class Employee extends Person {
  salary: number;
  constructor(name: string, age: number, salary: number) {
    super(name, age);
    this.salary = salary;
  }
}

const pCtor: typeof Person = Employee; // error, incompatible constructors
const p: Person = new Employee("", 0, 0); // no error, compatible instances

This means that p.constructor cannot be of type typeof Person without disrupting the mentioned code. While perhaps p.constructor could possess additional specificity beyond Function, current functionalities do not reflect this. Nonetheless, this does not impact the provided example scenario.


It's crucial to note that from a type system perspective, your code example mirrors this one identically. Despite differences in the resulting behavior of p instanceof Person, the structural compatibility remains unchanged. No extends clause is necessary for types to align. Even if an Employee class looks like this:

class Employee {
  name: string;
  age: number;
  salary: number;
  constructor(name: string, age: number, salary: number) {
    this.name = name;
    this.age = age;
    this.salary = salary;
  }
}

there won't be any alterations in TypeScript's behavior. An Employee's constructor property maintains a type of Function, resulting in every Employee being structurally compatible with Person.

Visit Playground link to view the code

Answer №2

When Type A is compatible with Type B, it means that an instance of Type A can function as an instance of Type B, supporting all necessary operations and methods.

An Employee can perform all operations on a Person, making it suitable to be assigned to a Person variable.

Incompatibility in constructors does not affect this relationship, since constructors are not operations performed on instances of the class. Constructors are not called on objects of type 'Person' or 'Employee'.

Furthermore, even without the constructor issue, the types are not identical: Employee instances cannot be assigned to variables of type Person, as Employee includes a salary field while Person does not. Attempting to use a Person where an Employee is expected may result in a TypeScript error for safety reasons, but using an Employee as a Person does not pose such risks.

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

The error message "ng: command not found" popped up despite successfully installing the latest @angular/cli using npm linking

Here is the information about my current setup: Node version: v10.15.3 NPM version: 6.4.1 I attempted to run the following command: Command: npm i -g angular/cli An error occurred while executing: npm ERR! /usr/local/bin/git ls-remote -h -t ssh:// ...

Get the download URL from Firebase Storage and save it into an array within Firestore

I am currently working on a project to upload multiple image files to Firebase Storage and then store their download URLs in a single array within Firestore. uploadImages(name, images) { for (let i = 0; i < images.length; i++) { const file = ...

A more efficient method for defining and utilizing string enums in Typescript

enum PAGES { HOME = 'HOME', CONTACT = 'CONTACT', } export const links: { [key: string]: string } = { [PAGES.HOME]: '/home', [PAGES.CONTACT]: '/contact', }; export function getLink(page: string) { return B ...

How to dynamically retrieve values from a const object literal using TypeScript

Currently, I am utilizing a TypeScript library known as ts-proto, which is responsible for generating TypeScript code. The resulting generated code resembles the following: //BasicMessage.ts export interface BasicMessage { id: Long; name: string; } ...

Tips for creating a Single Xpath instead of Multiple paths

I'm currently working with Protractor alongside TypeScript. I have an element located at: xpath = "**//div[@class='loglist']/div[1]/div[2]**" Afterwards, there are additional elements that need to be identified in different divs s ...

What is the method for adjusting the time format?

Using the TIME data type, my data is currently displayed in the format hh:mm:ss (03:14:00). How can I change it to display in the format hh:mm (03:14)? The usual DATE type method does not seem to work: {{test.time | date: 'HH:mm'}} However, thi ...

Tips for updating the value.replace function for the "oninput" attribute within Angular 7

I need to modify an input based on a value from a TypeScript variable in the oninput attribute. This modification should only apply to English characters. In my HTML file: <input class="form-control" oninput="value=value.replace(r ...

Improved method for linking two enums with similar appearances

Currently, I use two enums as shown: enum Tab { Approved = "Approved", Pending = "Pending", Sold = "Sold", } enum ProductStatus { Approved = "Approved", Pending = "Pending", Sold = "Sold&q ...

Type narrowing not functioning as expected based on the specific data types

I've encountered an issue with type narrowing in my code that is leading to a compiler error. Strangely, making subtle changes to the types involved allows the compiler to pass without errors. These types have some overlap and include optional attribu ...

strictBindCallApply causing issues when working with generic parameters

Take a look at this slightly contrived code snippet: export function evaluate<T>(this: { value: T }) { return this.value; } class SomeClass { value: ''; constructor() { const result = evaluate.call(this); } } You might notice ...

Strategies for Handling Errors within Observable Subscriptions in Angular

While working with code from themes written in the latest Angular versions and doing research online, I've noticed that many developers neglect error handling when it comes to subscription. My question is: When is it necessary to handle errors in an ...

The base URL specified in the tsconfig file is causing the absolute path to malfunction

Displayed below is the layout of my folders on the left, along with a metro error in the terminal and my tsconfig.json configuration with baseUrl set to './src'. Additionally, I have included screenshots of my app.ts and MainTabs.ts for further c ...

Utilize localStorage.getItem() in conjunction with TypeScript to retrieve stored data

Within my codebase, I have the following line: const allGarments = teeMeasuresAverages || JSON.parse(localStorage.getItem("teeMeasuresAverages")) || teeMeasuresAveragesLocal; Unexpectedly, Typescript triggers an alert with this message: Argument ...

`Angular Image Upload: A Comprehensive Guide`

I'm currently facing a challenge while attempting to upload an image using Angular to a Google storage bucket. Interestingly, everything works perfectly with Postman, but I've hit a roadblock with Angular Typescript. Does anyone have any suggesti ...

Encountering a syntax error when attempting to utilize the colon symbol for specifying data types

Currently, I am a novice who is delving into the world of TypeScript. Here is a snippet of code that I have written: let num: number = 123; console.log(123); However, when attempting to run this file using Node.js and saving it as test.ts, I encounter the ...

Decrease initial loading time for Ionic 3

I have encountered an issue with my Ionic 3 Android application where the startup time is longer than desired, around 4-5 seconds. While this may not be excessive, some users have raised concerns about it. I am confident that there are ways to improve the ...

The BooleanField component in V4 no longer supports the use of Mui Icons

In my React-Admin v3 project, I had a functional component that looked like this: import ClearIcon from '@mui/icons-material/Clear' import DoneIcon from '@mui/icons-material/Done' import get from 'lodash/get' import { BooleanF ...

Using React.ReactNode as an argument in Storybook

This is a unique button component type that I have created import React from 'react' export type ButtonProps = { label: string; color?:'primary' | 'secondary' | 'tertiary'; size?:'mobile' | 'tabl ...

Chai expect() in Typescript to Validate a Specific Type

I've searched through previous posts for an answer, but haven't come across one yet. Here is my query: Currently, I am attempting to test the returned type of a property value in an Object instance using Chai's expect() method in Typescript ...

Interface displaying auto-detected car types

I have a setup that looks like this: interface ValueAccessor<T> { property: keyof T; getPropertyValue: (value: any) => value; } I am trying to figure out how to define the correct type and replace the any when I want to provide a custom ...