Verifying data types in TypeScript

When working with TypeScript in the browser, I often find myself writing code like this:

const button = document.getElementById(id);
if (!(button instanceof HTMLButtonElement)) {
  throw new Error("TODO -- insert better error message here");
}
button.disabled = false;

The throw statement is necessary because getElementById() returns type HTMLElement | null, which doesn't support the disabled property. After the throw, the type changes to HTMLButtonElement. While I could use a type assertion, I prefer including a runtime check in this code.

I wonder if there's a way to encapsulate this logic in a function, such as:

const button = verify(document.getElementById(id), HTMLButtonElement);
button.disabled = false;

or

const button = verify<HTMLButtonElement>(document.getElementById(id));
button.disabled = false;

But not like:

const button = verify<HTMLButtonElement>(document.getElementById(id), HTMLButtonElement);
button.disabled = false;

because that would involve repeating the same word and lead to potential mistakes.

In languages like Java or C#, I would use

(HTMLButtonElement)document.getElementById(id)
instead of verify(). In C++, it might be something like
dynamic_cast< HTMLButtonElement & >(document.getElementById(id))
. Essentially, I'm looking to perform the runtime check while also satisfying the compiler, all while minimizing unnecessary typing.

Answer №1

Surprisingly, this turned out to be a bit more challenging than I initially thought, but the solution works fine:

function validateElement<T extends Element>(element: Element | null, type: {new(): T}): T {
    if (element === null) {
        throw new Error("TODO: element is null");
    }
    if (element instanceof type) {
        return element;
    } else {
        throw new Error("TODO: wrong type");
    }
}

Interactive Code Playground

For testing the accuracy, here's a code snippet:

function validateElement(element, type) {
  if (element === null) {
    throw new Error("TODO: element is null");
  }
  if (element instanceof type) {
    return element;
  } else {
    throw new Error("TODO: wrong type");
  }
}

function activateLink(identifier) {
  const link = verify(document.getElementById(identifier), HTMLLinkElement);
  link.disabled = false;
}

activateLink("link");
try {
  activateLink("non_existing_link");
} catch (error) {
  console.error(error);
}
try {
  activateLink("not_a_link");
} catch (e) {
  console.error(e);
}
a:disabled {
  color: grey;
}
<div id="not_a_link"></div>
<a id="link" disabled>Active Link</a>
<a id="inactive_link" disabled>Inactive Link</a>

Answer №2

function validateIfHTMLButton(value: any): boolean {
    return value instanceof HTMLButtonElement;
}

This function checks if the given value is an instance of HTMLButtonElement.

If you are confident that the element you're querying for will be an HTMLButtonElement, you can simply use:

const button = document.getElementById(id) as HTMLButtonElement;

Answer №3

This is derived from the approved response.

/**
 * The following function acts as a wrapper for document.getElementById().
 * It guarantees that we locate the element and ensure it has the correct type, otherwise an exception is thrown.
 * Take note that the return type of the function aligns with the requested type.
 * @param id Search for an element with this specific id.
 * @param ty This represents the expected type, for example, HtmlButtonElement
 */
function fetchById<T extends Element>(id : string, ty: {new(): T}): T {
  const located = document.getElementById(id);
  if (!located) {
    throw new Error("Could not find element with id " + id + ".  Expected type:  " + ty.name);
  }
  if (located instanceof ty) {
    return located;
  } else {
      throw new Error("Element with id " + id + " has type " + located.constructor.name + ".  Expected type:  " + ty.name);
  }
}

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

TypeScript Color Definitions in React Native

I'm working on a component that requires users to pass only valid color values using TypeScript type checking in a React Native project. How can I achieve this and which types should I use? const TextBody = ({ color }: {color: //Need This}) => { ...

Develop a custom cell editor for ag-Grid and insert it into a designated location within

I am currently working with Angular 16 and AgGrid 30. My goal is to have the cell editor created in a different location than its default position, which is within a div element at the bottom of the body with these classes: ag-theme-material ag-popup. I w ...

Develop a type definition utilizing dotted paths from a recursive object model

Working with TypeScript, I am dealing with a nested object structure of functions defined as: type CallbackFn = (args: any) => any type CallbackObj = { [key: string]: CallbackFn | CallbackObj } const callbacks = { foo: function(args: { x: num }): st ...

Using Vue 2 with a personalized Axios setup, integrating Vuex, and incorporating Typescript for a robust

I'm still getting the hang of Typescript, but I'm facing some challenges with it when using Vuex/Axios. Current setup includes: Vue CLI app, Vue 2, Vuex 3, Axios, Typescript At a high level, I have a custom Axios instance where I configure the ...

I am looking to conceal the y-axis labels and tooltip within the react chart

I am currently working with react-chart-2. I have a line graph that displays a tooltip when hovered over, but I would like to hide this tooltip feature. Additionally, I want to remove the numbers 0, 0.1, 0.2 up to 1 on the left side (y-axis) of the gra ...

Loop through a collection of map instances in TypeScript

In my TypeScript code, I am making a call to an API method in a Java class that returns a list of maps. The TypeScript file includes the code snippet below. When attempting to retrieve data from dataBody, it displays as [Object Object]. I need assistance ...

Error in TypeScript when utilizing generic callbacks for varying event types

I'm currently working on developing a generic event handler that allows me to specify the event key, such as "pointermove", and have typescript automatically infer the event type, in this case PointerEvent. However, I am encountering an error when try ...

Recursive types in TypeScript allow for the definition of types that

Is there a way to implement the function below without utilizing any? Playground type MyType = { name: string, age: number, score: { prime: number, }, prize: { first: { discount: number } } } export const trim = ( myObj: ...

Sharing precise API information between React components in React Components

I am currently learning react and facing an issue with sending data to other component files. I am trying to verify a user login from a backend API within an if statement, and if successful, I want to send the user ID to another component file. However, ...

Is it possible to dynamically adjust the size of the CircleProgressComponent element in ng-circle-progress?

For my current Angular 11 project, I am facing the challenge of dynamically changing the size of the ng-circle-progress library's CircleProgressComponent element. After some research, I discovered that the element's size can be adjusted by apply ...

Combining Rollup, Typescript, and converting images to base64 during the loading process

Having trouble preloading an image with Rollup. None of the solutions that should work seem to be effective, and I can't figure out why. Has anyone successfully managed to make this work? Here is my configuration in rollup.config.js: import image fr ...

Transform JSON object to a class/interface object using Typescript

I am currently working on converting an API response into a TypeScript class or interface. The API is returning a list of objects with various properties, but I only require a few specific properties from the response object. Example of API Response: ...

Is there a way to restrict an array to only accept distinct string literals?

export interface IGUser { biography: string; id: string; ig_id: string; followers_count: number; follows_count: number; media_count: number; name: string; profile_picture_url: string; shopping_product_tag_eligibility: boolean; username: ...

When using Playwright, there may arise a requirement to reuse a specific UUID that has been defined in one test within another

I have two separate tests running in parallel, each creating a different company in my test environment. However, I need to access the uuid of both companies in later tests. I am looking for a way to store these uuids so they can be used across all subseq ...

Tips on implementing zod schema types with remapped fields using the toZod method

I'm currently working on mapping a schema key to a different name in my database interface Country { isoCode: string, nameEn: string, nameDe: string, phone: string, bla: string } const CountryJson = { i ...

Tips for maintaining knowledge after redirecting to a new page

Building an app using Ionic 4 where I need to display vouchers from a database as images. Each image should act as a link to a details page showing more information about that specific voucher. However, I am struggling to figure out how to keep track of th ...

Sharing enums between client and server code in Webpack is a smart way to improve

I'm currently working on a project setup that looks like this: | |--public-|file1.ts | |enum.ts | |--server/file2.ts | The issue I am facing is incorporating the enum defined in enum.ts into both file1 and file2. While file1 can import and u ...

Invoking a nested class while declaring types in TypeScript

This is the specific format of data that I am in need of this.structure=[ { id: 1, name: 'root1', children: [ { id: 2, name: 'child1' }, { id: 3, name: 'child2' } ] }, { ...

Utilize Angular's Reactive Form feature to track changes in Form Control instances within a Form Array and calculate the total value dynamically

I am currently utilizing Angular Reactive Forms to loop through an array of values and I want to include a total field after the Form Array that automatically updates whenever there are changes in the Form Array control values. Here is some sample data: ...

How to set return types when converting an Array to a dynamic key Object in Typescript?

Can you guide me on defining the return type for this function? function mapArrayToObjByKeys(range: [string, string], keys: { start: string; end: string }) { return { [keys.start]: range[0], [keys.end]: range[1] } } For instance: mapArrayToObj ...