Is it feasible to determine the precise class of an object while utilizing a variable that possesses a union type in Typescript?

Currently delving into Typescript and running tests. Upon using console.log, I received an object. Given its typed nature, I anticipated it to accurately determine the type of variable c.

interface Car {
  gears: number
}

interface Bike{
  gears: number,
  model: string
}

let c: Bike | Car;

c = {gears : 2};

console.log(typeof c);

Answer №1

At compile time, TypeScript retains type information but discards it at runtime.

It's important to note that the typeof method in JavaScript operates as JavaScript, not TypeScript.

You can refer to the return values of typeof here. The TypeScript documentation also confirms this.

If you're interested in "type checking," consider exploring narrowing, as there are various ways to achieve it.

Remember that TypeScript employs structural typing rather than nominal typing, like C# and Java. This means that two objects don't necessarily have to be of the same type; they just need to be compatible enough (usually having overlapping methods/fields).

Answer №2

typeof is a unique JavaScript operator that provides the type of a specific variable during runtime. In contrast, TypeScript allows you to define types only during code writing and compilation. During runtime, JavaScript offers native types such as: Boolean, Null, Undefined, Number, BigInt, String, Symbol, and Object.

With that being said, when differentiating between Car and Bike when utilizing them as a union type, a common approach is to implement a discriminator property. For example:

interface Car {
  gears: number,
  type:"car"
}

interface Bike{
  gears: number,
  model: string,
  type:"bike"
}

// demonstrating a use case
function foo(bar: Car | Bike ) {
  if(bar.type === "bike"){
    // TypeScript permits access to the model property only here 
    console.log(bar.model); 
  }
}

Answer №3

Typescript introduces a static type system at compile time. When transpiled to Javascript, the type information is lost, leaving only what JS inherently understands.

Using interface or type in Typescript does not have direct equivalents in Javascript. However, by utilizing classes with prototype chains, such as shown below, you can differentiate types at runtime using instanceof.

class Car {
    constructor(public readonly gears: number) {}
}

class Bike{
    constructor(
        public readonly gears: number, 
        public readonly model: string
    ) {}
}

let c: Bike | Car;

c = new Car(2)

console.log(c instanceof Car)
console.log(c instanceof Bike);

This approach addresses the runtime distinction between types but does not resolve the issue of accessing properties that may vary based on the specific type at compile time.

In cases where we have a union like Bike | Car and attempt to access a property unique to one type while applied to another, a compile-time error would be generated.

console.log(c.model)
    //        ^^^^^ Property 'model' does not exist on type 'Car'

To address this challenge, when utilizing interface, it's essential to provide mechanisms for discerning between various types. This allows Typescript to recognize when a variable's type is narrower than its declaration. If two types share identical shapes, distinguishing them becomes impossible without a discriminant property to provide subtle hints. In the provided example, Bike differs from Car due to its model property, enabling us to create a 'type predicate' for both compile-time and runtime inference.

function isBike(obj: any): obj is Bike {
    return typeof obj === 'object' && typeof obj.model === 'string'
}

We may also define an inverse function:

function isCar(obj: any): obj is Car {
    return typeof obj === 'object' && typeof obj.model !== 'string'
}

Alternatively, we could consider:

function isCar(obj: any): obj is Car {
    return typeof obj === 'object' && typeof obj.model === undefined
}

The method of discrimination between types is subjective and depends on the context. It is possible to base the differentiation on property values rather than just their types or presence within the object.

By adopting these type predicates, we safeguard the usage of Bike's properties, promoting type safety during compilation and runtime execution.

if (isBike(c)) {
  console.log(c.model)
}

if (isCar(c)) {
  console.log(c.model)
      //        ^^^^^ Property 'model' does not exist on type 'Car'
}

Discriminated Unions present a similar solution. While type predicates rely on accurate implementation, there is a risk of inconsistencies when codebase modifications are made without updating the predicates accordingly, potentially resulting in misinterpretations. Nonetheless, this method offers flexibility in handling type unions lacking a common discriminator property.

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

A single element containing two duplicates of identical services

I am encountering an issue with my query builder service in a component where I need to use it twice. Despite trying to inject the service twice, it seems that they just reference each other instead of functioning independently, as shown below: @Component( ...

The compilation fails when using TestUtils findRenderedComponentWithType with a Component<Props> definition

Content of the file package.json: { "scripts": { "test": "tsc --project . --noEmit" }, "dependencies": { "@types/react": "^16.7.6", "@types/react-dom": "^16.0.9", "react": "^16.6.3", "react-dom": "^16.6.3", "typescript": "^3. ...

The NodeJS namespace does not include the exported member "Global." It is recommended to replace NodeJS.Global with typeof globalThis

Can you show the proper way to utilize typeof globalThis? Please provide a detailed explanation, without taking any shortcuts. interface CustomNodeJsGlobal extends NodeJS.Global { foo: { bar: string | null } } Upon execution, you may encounter the ...

Obtaining an array of objects through the reduction of another array

Currently, my goal is to extract an array of objects from another array. Let's say I have an array consisting of strings: const strArr = ["Some", "Thing"]; The task at hand is to create a new Array containing 2 objects for each st ...

What is the reason for a high-order generic function to eliminate falsy types from the argument list of the returned function?

Consider this example of a unique Decorator Factory Builder - a builder that constructs Decorator Factories to define metadata for forms: interface FormFieldOptions { name?: string; label?: string; format?: string; } type FormProperties = Record< ...

Top tips for accessing and modifying an immutable object within a component retrieved from an RXJS/NGRX store in Angular

This week we successfully updated our Angular v9 app to v11 and RXJS v6.6 with minimal issues. However, due to the store being in freeze mode, we are encountering errors when trying to update the store in certain areas of our code. While most of the issue ...

Vuejs fails to properly transmit data

When I change the image in an image field, the new image data appears correctly before sending it to the back-end. However, after sending the data, the values are empty! Code Commented save_changes() { /* eslint-disable */ if (!this.validateForm) ...

Manipulating Data in TypeScript: Creating a Mutated Copy of a List of Dictionaries

After going through multiple answers, it appears that there might be a logical error. However, I am struggling to find a solution for this issue. In TypeScript/JavaScript, I have two lists of dictionaries. One list is a copy of the other for tracking purp ...

Guide on transferring the Token from the initial response request to the header of the second request, with the help of Axios in an Ionic React application (Making 2 Post Requests

I am trying to create a user account and save the partner's data simultaneously. The initial axios request is used to create the user and obtain a token in return. I need to pass this token as a header in the second request. Despite implementing &apos ...

What is the best way to incorporate an external .css file into my Angular project by referencing its URL?

I'm managing a collection of CSS files online and I need to incorporate each one into my project based on its specific requirements. One component in particular is connected to different numerical IDs in the router. I am looking for a way to dynamica ...

Instructions for disabling editing for a specific cell within an inline editable row in primeNG

I am currently using PrimeNG DataTable with Angular, where the rows are editable as shown in the example in the documentation: https://www.primefaces.org/primeng/#/table/edit. However, I am facing an issue where I want to exclude one cell from being editab ...

Error: Unexpected top-level property "import/order" in ESLINT configuration

I'm in the process of setting up a guideline to include one blank line between internal and external imports. .eslintrc.json: { "parser": "@typescript-eslint/parser", "env": { "es6": true, " ...

When TypeScript error "ts(18004)" occurs, it is because of the object properties within all Prisma DB

I am currently in the process of verifying if a user's email already exists. To achieve this, I am utilizing Prisma Client's findUnique method. Below is the code snippet I have implemented: const userWithEmail = await prisma.user.findUnique({ ...

Ways to retrieve data object within an HTMLElement without relying on jQuery

Within my web application, I have successfully linked a jQuery keyboard to a textbox. Now, I am seeking a way to explicitly call the keyboard.close() function on the keyboard as I am removing all eventListeners from the text field early on. To achieve thi ...

Ways to determine the types of props received by a function when the arguments vary for each scenario?

I have a specialized component that handles the majority of tasks for a specific operation. This component needs to invoke the onSubmit function received through props, depending on the type of the calling component. Below is an example code snippet show ...

How to Publish an Angular 8 Application on Github Pages using ngh

Currently in my angular 8 project, I am encountering the following issue while running the command: ole@mkt:~/test$ ngh index.html could not be copied to 404.html. This does not look like an angular-cli project?! (Hint: are you sure that you h ...

Issue: Unable to find 'fs' in JS universal application

I am currently developing a Typescript package that needs to be compatible with both front end and back end environments. To determine the environment (browser or node), I am utilizing the following npm package: https://www.npmjs.com/package/browser-or-nod ...

Bring in jspm libraries to your project via typescript

While researching how to import jspm packages into typescript, I noticed that most examples assumed the use of SystemJS for loading and interpreting them in the browser. However, I prefer using tsc to compile commonjs modules and only import the js code, a ...

Can ng-packagr create scripts that are compatible with running in a web browser like regular JavaScript?

Is it feasible to utilize ng-packagr to compile a library into a single script file that can be executed on a web browser by importing it as <script src="bundle.js"></script>? For instance, if I have a main.ts file that contains cons ...

What is the best method for transferring data from a parent to a child component in Angular?

Is there a way to share a string variable with parent and child components in Angular (TypeScript) without the child component updating automatically when the input variable is updated? I want the child component to only update when the data is sent from t ...