I'm using the following function to search HTML elements by text:
function findElementsByText(text: string): HTMLElement[] {
const selectors = 'button'
// without this, when `text` is empty, all the elements on the page will match
const pattern = new RegExp(text === '' ? '^$' : text)
return Array.from(document.querySelectorAll(selectors)).filter(
(element) => element.innerText.match(pattern)
)
}
I don't want to only search buttons, so I added anchor tags:
const selectors = 'a, button'
But, strangely, now I get this TypeScript error:
Type 'Element[]' is not assignable to type 'HTMLElement[]'. Type 'Element' is missing the following properties from type 'HTMLElement': accessKey, accessKeyLabel, autocapitalize, dir, and 116 more.
Why did adding one more selector cause this, and how to remove the error?
Live code:
CodePudding user response:
Your code did work, because querySelector
has an overload which returns HTMLButtonElement
list
function findElementsByText(text: string): HTMLElement[] {
const selectors = 'button'
// without this, when `text` is empty, all the elements on the page will match
const pattern = new RegExp(text === '' ? '^$' : text)
return Array.from(document.querySelectorAll(selectors)).filter(
// ^?
// (method) ParentNode.querySelectorAll<"button">(selectors: "button"): NodeListOf<HTMLButtonElement> ( 2 overloads)
(element) => element.innerText.match(pattern)
)
}
If your selector is not a single tag name, you need to use as HTMLElement[]
or provide generic type
function findElementsByText2(text: string): HTMLElement[] {
const selectors = 'button, a'
// without this, when `text` is empty, all the elements on the page will match
const pattern = new RegExp(text === '' ? '^$' : text)
// vvvvvvvvvvv
return Array.from(document.querySelectorAll<HTMLElement>(selectors)).filter(
(element) => element.innerText.match(pattern)
)
}
CodePudding user response:
TypeScript is smart enough that it can recognise that the string "button" will give a NodeList of HTMLButtonElements
elements (you can tell by hovering over querySelectorAll
in the link you provided).
Here is the type definition for it:
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 listed in HTMLElementTagNameMap
…
interface HTMLElementTagNameMap { "a": HTMLAnchorElement; "abbr": HTMLElement;
… so the first definition matches and it returns NodeListOf<HTMLElementTagNameMap[K]>
Once you change the selector to something more complex, it doesn't match anything (because it compares the whole string, it can't parse it at this point) so it falls back to the last definition which returns
NodeListOf<E>
where E
has just been defined as something that extends Element
.
Since the elements might be from a non-HTML source, it can't be HTMLElement
, hence the error.
The simple workaround is to use a generic type to override that:
querySelectorAll<HTMLElement>(selectors)