Combining Interfaces in Typescript: Utilizing Union Types with a Base and Extended Interface

I'm facing an issue with the following code snippet

interface BaseA {
  a: number;
}

interface SpecialA extends BaseA {
  b: number;
}

type A = BaseA | SpecialA

const a = {
    a: 5, b: 5
} as A

console.log(a.b)

Even though I thought the code was correct, I encountered the error message

Property 'b' does not exist on type 'A'.
  Property 'b' does not exist on type 'BaseA'

It appears that the actual definition of type A is not what I intended it to be. I was expecting it to match the definition below:

interface A {
  a: number;
  b?: number;
}

This leaves me with the following questions:

  1. What caused the discrepancy between the defined type A and the expected type A?
  2. Is there a way to define the expected type A without manually defining it?

Note: I am required to use the type SpecialA unchanged in certain parts of my code, so directly defining the expected A type is not feasible.

Answer №1

Given that A is a union, it is important to narrow down/discriminate your type in order to access members that are not shared.

interface BaseA {
  a: number;
}

interface SpecialA extends BaseA {
  b: number;
}

type A = BaseA | SpecialA

const a = {
  a: 5, b: 5
} as A

if ("b" in a) { // performing narrowing at this point
  console.log(a.b)
}

Playground

Answer №2

When you use as A, you are essentially stating that "The variable a could potentially hold a BaseA or a SpecialA." However, if you later attempt to access a.b without any type check, it becomes a type error because TypeScript cannot guarantee that a is specifically a SpecialA which contains the b property.

To address this issue, you should perform a check first:

if ("b" in a) {
    console.log(a.b);
}

Within the if block, TypeScript understands that a points to an instance of SpecialA.

It's worth noting that even though a is declared as a constant, the object it refers to can still be altered to remove the b property:

delete (a as any).b; // not recommended

If you do not intend to delete the b property, it's advisable to directly specify the type as SpecialA:

const a: SpecialA = {
    a: 5,
    b: 5,
};

Alternatively, you can use as const to indicate to TypeScript that the object will remain unaltered:

const a = {
    a: 5,
    b: 5,
} as const;

If you choose to go with as const, there is no need for a separate type annotation. TypeScript conducts structural type checking rather than nominal type checking, but adding a type like A can enhance clarity for other developers:

const a: A = {
//     ^^^
    a: 5,
    b: 5,
} as const;

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

Is it possible for a typed function to access object properties recursively?

Is there an efficient method in TypeScript to type a function that can recursively access object properties? The provided example delves two levels deep: function fetchNestedProperty<T, K extends keyof T>(obj: T, prop1: K, prop2: keyof T[K]) { r ...

In TypeScript, encountering an unanticipated intersection

In my "models" registry, whenever I select a model and invoke a method on it, TypeScript anticipates an intersection of parameters across all registered models. To demonstrate this issue concisely, I've created a dummy method called "getName". expor ...

Extract a string value from a TypeScript enum

Here is a basic enum definition: export enum Type { TEST_ONE = "testing.one", TEST_TWO = "testing.two", BETA = "beta.one" } I am looking to run a function for each string value in the enum. For example: executeType(type: string) { console.lo ...

Using TypeScript to Import Modules without Default Exports (CommonJS)

Can a module that is defined without a default export be imported using import module from 'module'; and then compiled to commonjs? An answer on Stack Overflow suggests that it might be possible with the use of the --allowSyntheticDefaultImports ...

Error message: WebStorm shows that the argument type {providedIn: "root"} cannot be assigned to the parameter type {providedIn: Type<any> | "root" | null} and InjectableProvider

Transitioning my app from Angular v5 to v6 has presented me with a TypeScript error when trying to define providedIn in my providers. The argument type {providedIn: "root"} cannot be assigned to the parameter type {providedIn: Type | "root" | null} & ...

Utilizing useLocation for Defining Text Styles

I'm currently integrating TypeScript into my project, but I'm encountering an error related to 'useLocation' in my IDE. Any thoughts on what might be causing this issue? import React from "react"; import { useHistory, useLocat ...

Error encountered when attempting to assign a value of the original data type within the Array.reduce function

I am in the process of developing a function that takes a boolean indicator object like this: const fruits = { apple: false, banana: false, orange: false, mango: false, }; Along with an array such as ['apple', 'orange']. The go ...

The program encountered an unexpected symbol. It was expecting a constructor, method, accessor, or property. Additionally, there is a possibility that the object is 'undefined'

Can someone please help me figure out what's wrong with this code snippet? import cassandra from "cassandra-driver"; class Cass { static _cass : cassandra.Client; this._cass = new cassandra.Client({ contactPoints: ['localhost&ap ...

Exploring Angular 2: Incorporating multiple HTML pages into a single component

I am currently learning Angular 2 and have a component called Register. Within this single component, I have five different HTML pages. Is it possible to have multiple templates per component in order to navigate between these pages? How can I implement ro ...

Can you identify the type of component being passed in a Higher Order Component?

I am currently in the process of converting a protectedRoute HOC from Javascript to TypeScript while using AWS-Amplify. This higher-order component will serve as a way to secure routes that require authentication. If the user is not logged in, they will b ...

Canceling a promise in a Vuex action

I am looking to implement the ability to cancel a running promise in my Vue component, specifically a promise returned by a Vuex action. In my scenario, the Vuex action is continuously polling an endpoint for status updates, and I need the capability to s ...

Angular 2 Unit test issue: Unable to resolve parameters for 'RequestOptions' class

I am currently working on testing a simple component that has some dependencies. One of the requirements is to provide certain providers for the test. /* tslint:disable:no-unused-variable */ import { By } from '@angular/platform-browser&ap ...

A guide on how to identify the return type of a callback function in TypeScript

Looking at this function I've created function computedLastOf<T>(cb: () => T[]) : Readonly<Ref<T | undefined>> { return computed(() => { const collection = cb(); return collection[collection.length - 1]; }); } Thi ...

Is it time to refresh the sibling element when it's selected, or is there

I have recently started working with react and TypeScript, and I am facing some challenges. My current task involves modifying the functionality to display subscriptions and transactions on separate pages instead of together on the same page. I want to sh ...

What is the reason behind Flow's reluctance to infer the function type from its return value?

I was anticipating the code to undergo type checking within Flow just like it does within TypeScript: var onClick : (() => void) | (() => boolean); onClick = () => { return true; } However, I encountered this error instead: 4: onClick = () => ...

Filtering arrays of objects dynamically using Typescript

I am looking to create a dynamic filter for an array of objects where I can search every key's value without specifying the key itself. The goal is to return the matched objects, similar to how the angular material table filters all columns. [ { ...

Storing a Vue/JS element reference in a constant using Typescript

In my template, I have one form element and one button element: <button type="submit" id="ms_sign_in_submit" ref="submitButton" class="btn btn-lg btn-primary w-100 mb-5"> </button> Wi ...

Incorporating interactive buttons within Leaflet popups

I'm facing an issue with adding buttons to a Leaflet popup that appears when clicking on the map. My goal is to have the popup display 2 buttons: Start from Here Go to this Location The desired outcome looks like this sketch: ___________________ ...

How to conditionally apply a directive to the same tag in Angular 4

I am implementing angular 4 and have a directive in my template for validation purposes. However, I would like to first check if a specific condition is true before applying the directive. Currently, my code looks like this: <div *ngIf="groupCheck; els ...

The type definition file for '@types' is not present in Ionic's code base

After updating my Ionic 6 project to use Angular 3, everything works perfectly in debug mode. However, when I attempt to compile for production using 'ionic build --prod' or 'ionic cordova build android --prod', I encounter the followin ...