TypeScript causing issues when multiple selectors are used

Currently, I have a function that allows me to search for HTML elements based on text content:

function findElementsByText(text: string): HTMLElement[] {
  const selectors = 'button'
  // This conditional statement ensures that an empty text query doesn't match all elements on the page
  const pattern = new RegExp(text === '' ? '^$' : text)

  return Array.from(document.querySelectorAll(selectors)).filter(
    (element) => element.innerText.match(pattern)
  )
}

I decided to broaden the search by incorporating anchor tags into the selection criteria:

  const selectors = 'a, button'

However, this modification triggered a TypeScript error:

The error message states: 'Type 'Element[]' is not assignable to type 'HTMLElement[]'. Type 'Element' is missing various properties from type 'HTMLElement': accessKey, accessKeyLabel, autocapitalize, dir, and more.

Why did including another selector cause this issue, and what steps can be taken to resolve it?

To examine the live code example, please refer to the following link:

https://codesandbox.io/s/typescript-selectors-yhu1j1?fontsize=14&hidenavigation=1&theme=dark

Answer №1

TypeScript's intelligence allows it to identify that the string "button" will result in a NodeList of HTMLButtonElements elements (you can confirm this by hovering over querySelectorAll in the provided link).

Below is the type definition for this behavior:

querySelectorAll<K extends keyof HTMLElementTagNameMap>(selectors: K): NodeListOf<HTMLElementTagNameMap[K]>;
querySelectorAll<K extends keyof SVGElementTagNameMap>(selectors: K): NodeListOf<SVGElementTagNameMap[K]>;
querySelectorAll<E extends Element = Element>(selectors: string): NodeListOf<E>;

"button" and "a" are both included in HTMLElementTagNameMap...

interface HTMLElementTagNameMap {
    "a": HTMLAnchorElement;
    "abbr": HTMLElement;

...which means the first definition matches and returns NodeListOf<HTMLElementTagNameMap[K]>

If you change the selector to something more complex, it won't match anything because it compares the entire string and cannot parse it at that point. This results in falling back to the last definition which returns NodeListOf<E> where E is defined as extending Element.

As the elements may come from a non-HTML source, they cannot be HTMLElement, hence the error.

A simple workaround is using a generic type to override this:

querySelectorAll<HTMLElement>(selectors)

Answer №2

Your implementation was successful because the querySelector method has an overload that returns a list of HTMLButtonElement

function findElementsByText(text: string): HTMLElement[] {
  const selectors = 'button'
  // to avoid matching all elements on the page when `text` is empty
  const pattern = new RegExp(text === '' ? '^$' : text)

  return Array.from(document.querySelectorAll(selectors)).filter(
    (element) => element.innerText.match(pattern)
  )
}

If your selector contains more than one tag name, you should use as HTMLElement[] or provide a generic type

function findElementsByText2(text: string): HTMLElement[] {
  const selectors = 'button, a'
  // to avoid matching all elements on the page when `text` is empty
  const pattern = new RegExp(text === '' ? '^$' : text)

  return Array.from(document.querySelectorAll<HTMLElement>(selectors)).filter(
    (element) => element.innerText.match(pattern)
  )
}

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 mat-slide-toggle component does not recognize the checked binding

My angular app contains the mat-slide-toggle functionality. switchValue: {{ switch }} <br /> <mat-slide-toggle [checked]="switch" (toggleChange)="toggle()">Toggle me!</mat-slide-toggle> </div> This is how the ...

How to effectively handle null in Typescript when accessing types with index signatures unsafely

Why am I getting an error that test might be potentially undefined even though I've enabled strictNullCheck in my tsconfig.json file? (I'm unsure of the keys beforehand) const a: Record<string, {value: string}> = {} a["test"].va ...

What is the reason TypeScript does not display an error when assigning a primitive string to an object String?

From my understanding in TypeScript, string is considered as a primitive type while String is an object. Let's take a look at the code snippet below: let s: string = new String("foo"); // ERROR let S: String = "foo"; // OK It's interesting to ...

Is there a way to make the Sweetalert2 alert appear just one time?

Here's my question - can sweetalert2 be set to only appear once per page? So that it remembers if it has already shown the alert. Swal.fire({ title: 'Do you want to save the changes?', showDenyButton: true, showCancelButton: true, ...

Error TS2322: Cannot assign type 'Foo | Bar' to type 'Foo & Bar'

I am attempting to save an item in an object using the object key as the discriminator for the type. Refer to the edit below. Below is a simple example: type Foo = { id: 'foo' } type Bar = { id: 'bar' } type Container = { foo ...

Flow error: Unable to access the value of this.props.width as the property width is not defined in T

In my React Native project, I am utilizing Flow for type checking. For more information, visit: I currently have two files named SvgRenderer.js and Cartoon.js where: Cartoon extends SvgRenderer Below is the source code for both of these files: SvgRend ...

Error Encountered: Angular 2 ngOnInit Method Being Executed Twice

Encountered an unusual error in Angular 2 while working on two components that share similarities in templates and services. Here is a breakdown of how they function: Component: data: any; constructor(private _service: TheService) {} ngOnInit() { t ...

Angular 2 integration for Oauth 2 popup authorization

I am in the process of updating an existing Angular application to utilize Angular 2. One challenge I am facing is opening an OAuth flow in a new pop-up window and then using window.postMessage to send a signal back to the Angular 2 app once the OAuth proc ...

angular-bootstrap-mdindex.ts is not included in the compilation result

Upon deciding to incorporate Angular-Bootstrap into my project, I embarked on a quest to find a tutorial that would guide me through the download, installation, and setup process on my trusty Visual Studio Code. After some searching, I stumbled upon this h ...

Is there a way to expand the return type of a parent class's methods using an object

Currently, I am enhancing a class by adding a serialize method. My goal is for this new method to perform the same functionality as its parent class but with some additional keys added. export declare class Parent { serialize(): { x: number; ...

Looking for guidance on locating Typescript type definitions?

As a newcomer to Typescript, I have recently delved into using it with React. While I have grasped the fundamentals of TS, I find myself perplexed when it comes to discovering or deriving complex types. For example, in React, when dealing with an input el ...

How to access parent slider variables in an Angular component

I've developed a slider as the parent component with multiple child components. See the demo here: https://stackblitz.com/edit/angular-ivy-gcgxgh?file=src/app/slide2/slide2.component.html Slider in the parent component: <ng-container *ngFor=&quo ...

Updating non-data properties dynamically in a custom AG Grid cell renderer

In my grid setup, I have implemented an editor button in a column for each row and a new item creator button outside the grid. One of the requirements is that all buttons should be disabled when either the create or edit button is clicked. To achieve thi ...

Ensuring consistency between TypeScript .d.ts and .js files

When working with these definitions: https://github.com/borisyankov/DefinitelyTyped If I am using angularJS 1.3.14, how can I be certain that there is a correct definition for that specific version of Angular? How can I ensure that the DefinitelyTyped *. ...

Transforming seconds into years, months, weeks, days, hours, minutes, and seconds

Can anyone help me modify Andris’ solution from this post: Convert seconds to days, hours, minutes and seconds to also include years, months, and weeks? I am currently running this code: getDateStrings() { console.log(req_creation_date); const toda ...

Tips for accessing an item from a separate TypeScript document (knockout.js)

In the scenario where I need to utilize an object from another TypeScript file, specifically when I have an API response in one.ts that I want to use in two.ts. I attempted exporting and importing components but encountered difficulties. This code snippe ...

Showing off the latest products at the top of the list

Typically, when utilizing ngFor, the most recent item is displayed underneath the initial element. For instance, a list containing: [Apple, Orange, Banana] If we use ngFor to display this list: Apple Orange Banana I am interested in learning a method t ...

The retrieval of cookies from the Response object is not possible with Typescript

While working on my google chrome extension, I implemented a post call that looks like this: public someapiCall(username: string, password: string) { var url = 'http://test.someTestServer.com/api/user/someApiCall'; let headers = new Hea ...

Sharing methods between two components on the same page in Angular can greatly improve code efficiency

On a single page, I have two components sharing some methods and properties. How can I utilize a service to achieve this? See the sample code snippet below... @Component({ selector: 'app', template: '<h1>AppComponent1</h1>' ...

Dynamic TypeScript property that can only be assigned values from an array during runtime

I'm currently struggling with specifying allowed values for a property in TypeScript. Within my interface, I have statically typed the property like this: interface SomeInterface{ prop: "bell" | "edit" | "log-out" } However, I am looking for a w ...