ChatGPT解决这个技术问题 Extra ChatGPT

The property 'value' does not exist on value of type 'HTMLElement'

I am playing around with typescript and am trying to create a script that will update a p-element as text is inputted in a input box.

The html looks as following:

<html>
    <head>
    </head>
    <body>
        <p id="greet"></p>
        <form>
            <input id="name" type="text" name="name" value="" onkeyup="greet('name')" />
        </form>
    </body>
    <script src="greeter.js"></script>
</html>

And the greeter.ts file:

function greeter(person)
{
    return "Hello, " + person;
}

function greet(elementId)
{
    var inputValue = document.getElementById(elementId).value;

    if (inputValue.trim() == "")
        inputValue = "World";

    document.getElementById("greet").innerText = greeter(inputValue);
}

When I compile with tsc I get the following "error":

/home/bjarkef/sandbox/greeter.ts(8,53): The property 'value' does not exist on value of type 'HTMLElement'

However the compiler does output a javascript file, which works just fine in chrome.

How come I get this error? And how can I fix it?

Also, where can I look up which properties are valid on a 'HTMLElement' according to typescript?

Please note I am very new to javascript and typescript, so I might be missing something obvious. :)


K
Kayce Basques

Based on Tomasz Nurkiewiczs answer, the "problem" is that typescript is typesafe. :) So the document.getElementById() returns the type HTMLElement which does not contain a value property. The subtype HTMLInputElement does however contain the value property.

So a solution is to cast the result of getElementById() to HTMLInputElement like this:

var inputValue = (<HTMLInputElement>document.getElementById(elementId)).value;

<> is the casting operator in typescript. See the question TypeScript: casting HTMLElement.

If you're in a .tsx file the casting syntax above will throw an error. You'll want to use this syntax instead:

(document.getElementById(elementId) as HTMLInputElement).value

The resulting javascript from the line above looks like this:

inputValue = (document.getElementById(elementId)).value;

i.e. containing no type information.


do you have any list which element of html belongs for which type for the typescript ? if yes then pls post it will be helpfull for somebody !! thanks for the great answer.
This fix breaks my code entirely; says whatever comes next "is not a function". I don't understand the reasoning behind the way TS handles this; since getElementById can return any element type, any should be accepted by default.
@TurtlesAreCute I'm curious about how this solution broke your code. Hopefully one year later you found a solution, but casting to HTMLInputElement should have worked.
In my case, I had to change it to HTMLAnchorElement.
@Wildhammer Because your element wasn't an Input element! The OP's is.
M
Michael

If you are using react you can use the as operator.

let inputValue = (document.getElementById(elementId) as HTMLInputElement).value;

For some reason, the accepted answer here was raising the following error for me: JSX element 'HTMLInputElement' has no corresponding closing tag. This is the answer that worked for me.
The reason is because in tsx files you cannot use the <> operators to cast, since these are used in React. So you have to use as: basarat.gitbooks.io/typescript/docs/types/…
Although this didnt work for me, it did direct me the right direction :) up vote!!!
I'm pretty sure the as operator is a TypeScript annotation, not a React. Thanks.
This worked well for me as I was trying to reset a form (document.getElementById("addUserForm") as HTMLFormElement).reset();
L
Leo

We could assert

const inputElement: HTMLInputElement = document.getElementById('greet')

Or with as-syntax

const inputElement = document.getElementById('greet') as HTMLInputElement

Giving

const inputValue = inputElement.value // now inferred to be string

w
woodysan

Try casting the element you want to update to HTMLInputElement. As stated in the other answers you need to hint to the compiler that this is a specific type of HTMLElement:

var inputElement = <HTMLInputElement>document.getElementById('greet');
inputElement.value = greeter(inputValue);

i
inDream

A quick fix for this is use [ ] to select the attribute.

function greet(elementId) {
    var inputValue = document.getElementById(elementId)["value"];
    if(inputValue.trim() == "") {
        inputValue = "World";
    }
    document.getElementById("greet").innerText = greeter(inputValue);
}

I just try few methods and find out this solution, I don't know what's the problem behind your original script.

For reference you may refer to Tomasz Nurkiewicz's post.


Proper answer to the question, helped me a lot.
T
Tomasz Nurkiewicz

The problem is here:

document.getElementById(elementId).value

You know that HTMLElement returned from getElementById() is actually an instance of HTMLInputElement inheriting from it because you are passing an ID of input element. Similarly in statically typed Java this won't compile:

public Object foo() {
  return 42;
}

foo().signum();

signum() is a method of Integer, but the compiler only knows the static type of foo(), which is Object. And Object doesn't have a signum() method.

But the compiler can't know that, it can only base on static types, not dynamic behaviour of your code. And as far as the compiler knows, the type of document.getElementById(elementId) expression does not have value property. Only input elements have value.

For a reference check HTMLElement and HTMLInputElement in MDN. I guess Typescript is more or less consistent with these.


But I am not accessing .value on the p element, I am accessing it on the input element. Or am I mistaken?
@bjarkef: you are calling document.getElementById("greet") and you have <p id="greet"></p>...
I am calling document.getElementById("greet").innerText which should be perfectly valid, I am only accessing the value property on the input element at the line containing: var inputValue = document.getElementById(elementId).value; in greet(elementId) and that is called in the html onkeyup="greet('name')" in the input form. So I think you are misreading the code, please correct me if I am wrong. :)
@bjarkef: sorry, you were right. I have rewritten my answer, have a look!
Ah, this makes sense. So should I cast the value returned from getElementById and how do I do that in typescript? Or is there another method than getElementById that returns HTMLInputElement ?
K
KidKode

Also for anyone using properties such as Props or Refs without your "DocgetId's" then you can:

("" as HTMLInputElement).value;

Where the inverted quotes is your props value so an example would be like so:

var val = (this.refs.newText as HTMLInputElement).value;
alert("Saving this:" + val);

e
emmaakachukwu

For those who might still be struggling with this, another alternative is using (document.getElementById(elementId) as HTMLInputElement).value = ''. source.

Should in case you still face issues with it then try extracting it to a function like:

function myInput() {
   (document.getElementById(elementId) as HTMLInputElement).value = ''
}

C
CertainPerformance

There is a way to achieve this without type assertion, by using generics instead, which are generally a bit nicer and safer to use.

Unfortunately, getElementById is not generic, but querySelector is:

const inputValue = document.querySelector<HTMLInputElement>('#greet')!.value;

Similarly, you can use querySelectorAll to select multiple elements and use generics so TS can understand that all selected elements are of a particular type:

const inputs = document.querySelectorAll<HTMLInputElement>('.my-input');

This will produce a NodeListOf<HTMLInputElement>.


I get the error in Vscode in a js file with checkJs true in tsconfig and target:Es2020. With querySelector and sending the input value, the fetch() to another website is not working, and with getelementbyid it works.What can I do?
You probably aren't using the right syntax. If you have getElementById('foo') and that works, then to translate to querySelector, you need querySelector('#foo') with the #. Then add generics with .querySelector<HTMLElement>('#foo')
R
Rohit Grover

If you are using angular you can use -

const element = document.getElementById('elemId') as HTMLInputElement;

t
touchmarine
const a = document.getElementById("a")
if (a instanceof HTMLInputElement) {
    // a.value is valid here
    console.log(a.value)
}

A Safer Way

The above code snippet is the gist of the anwser; continue reading for the reasoning.

Most existing answers recommended Type assertions (type casts) which do the job but are a bit like using any—which disables type checking. There is a better, safer way.

Type assertions are like telling TypeScript to pretend that a variable is of the type we say it is. As such, TypeScript will perform type checking for that type. If we've made a mistake and told it the wrong type, we will get a false sense of security as there will be no compilation warnings, but there will be errors at runtime. Let's look at an example:

// <input id="a" value="1">
// <div   id="b" value="2"></div>

const a = document.getElementById("a") as HTMLInputElement
const b = document.getElementById("b") as HTMLInputElement

console.log(a.value) // 1
console.log(b.value) // undefined

We've told TypeScript that a and b are of type HTMLInputElement and it will treat them as such. However, as b is of type HTMLDivElement which doesn't have the value property, b.value returns undefined.

Type Narrowing with Type Guards

A better way is using type guards which allow for greater control.

A type guard is a way of determining a variable's type at runtime. While type assertions just say: "variable x is of type T", type guards say: "variable x is of type T if it has these properties". Let's quickly see how a type guard looks:

const a = document.getElementById("a")
if (a instanceof HTMLInputElement) {
    // a == input element
} else {
    // a != input element
}

This type guard simply checks if a is an instance of HTMLInputElement. If it is, TypeScript will recognize that and treat a inside the if block as being that type—it will allow access to all the properties of the input element. This is called type narrowing.

Why should you use type guards over type assertions? For the same reason you handle errors. While type guards still won't output warnings at compile time, they put you in control. You (and TypeScript) cannot guarantee whether the value property exists, but with type assertions you can decide what to do in the case it doesn't. Type assertions are like ignoring errors and type guards are like handling them.

How to Use Type Guards

We will show three ways of fixing "The property does not exist" error. Each example uses the same HTML but it's included separately in each so it's easier to read.

Type assertions

// <input id="a" value="1">
// <div   id="b" value="2">

const a = document.getElementById("a") as HTMLInputElement // correct type assertion
const b = document.getElementById("b") as HTMLInputElement // incorrect type assertion
const c = document.getElementById("c") as HTMLInputElement // element doesn't exist

console.log(a.value) // 1
console.log(b.value) // undefined
console.log(c.value) // Uncaught TypeError: Cannot read property 'value' of null

Inline type guards

// <input id="a" value="1">
// <div   id="b" value="2">

const a = document.getElementById("a")
const b = document.getElementById("b")
const c = document.getElementById("c")

if (a instanceof HTMLInputElement) {
    console.log(a.value) // 1
}
if (b instanceof HTMLInputElement) {
    console.log(b.value)
}
if (c instanceof HTMLInputElement) {
    console.log(c.value)
}

b and c didn't log anything as they are not input elements. Notice that we didn't get any unexpected behavior as in the "type assertions" examples.

Note that this is a contrived example, usually you would handle the cases when types are not the ones you expected. I often throw if a type doesn't match, like so:

if (!(b instanceof HTMLInputElement)) {
    throw new Error("b is not an input element")
}
// b == input element (for the rest of this block)

Function type guards

This example is a little more advanced and a little unnecessary for this use case. However, it shows off function type guards and a more "flexible" type guard.

Function type guards are functions that determine whether the given value is of a certain type. They do so by simply returing a bool. For TypeScript to be able to understand that you are making a type guard, you must use a type predicate (see coment in example below).

// <input id="a" value="1">
// <div   id="b" value="2">

const a = document.getElementById("a")
const b = document.getElementById("b")
const c = document.getElementById("c")

if (hasValueProperty(a)) {
    console.log(a.value) // 1
}
if (hasValueProperty(b)) {
    console.log(b.value)
}
if (hasValueProperty(c)) {
    console.log(c.value)
}

const d = {
    "value": "d",
}
if (hasValueProperty(d)) {
    console.log(d.value) // d
}

type WithValue = {
    value: string
}

// hasValueProperty is a type guard function the determines whether x is of type
// WithValue.
//
// "x is WithValue" is a type predicate that tells TypeScript that this is a
// type guard.
function hasValueProperty(x: unknown): x is WithValue {
    return typeof x === "object" && x !== null && typeof (x as WithValue).value === "string"
}

Notice that, since our type guard is only checking for the presence of the "value" property, it can also be used for any other object (not just elements).


D
Dharman

This work for me:

let inputValue = (swal.getPopup().querySelector('#inputValue ')as HTMLInputElement).value

Needs Formatting
Plus what does swal even mean?
S
Stebenwolf

I've been having a similar issue (TS warning in JS file: "Property X does not exist on type X": is it possible to write cleaner JavaScript?)

While the tag helped remove the warning in the typescript file, I would still get a warning in my JavaScript file after compiling.

So how do I write code that is clean AND that allows me to manipulate the .value ?

It took me quite some time but I found the solution by using another method:

HTML code:

<form id="my-form" 
   action="index.html"
   method="get"
   onsubmit="return showValue();">
    <input type="text" name="username">
    <input type="text" name="full-name">
    <input type="password" name="password">
    <button type="button" onclick="showValue();">Show value</button>
</form>

Javascript code:

function showValue() {
    const myForm = document.forms.my-form;
    console.log(myForm?.username.value);
    return 1;
}

The document.forms.x has a property "value" and that removes warnings both in the typescript file and in the resulting JavaScript.


J
JaMondo

If you have dynamic element ID where you need to assign the dynamic value, you may use this:

//element_id = you dynamic id.
//dynamic_val = you dynamic value.
let _el = document.getElementById(element_id);
_el.value = dynamic_val.toString();

This works for me.


j
jfunk

This might seem trite, but this is what worked for me:

yarn add -D @types/web

I think I must have had an out of date type definition.


S
Stefano Zanini

Problem:

error Property 'text' does not exist on type 'HTMLElement'

Solution: in typeScript we need to cast document.getElementById() which returns type HTMLElement in < HTMLScriptElement >

So we can do it by by following way to resolve the error as expected by typescript.js

Code: var content: string = ( < HTMLScriptElement > document.getElementById(contentId)).text;

It worked for me.. hope it works for you as well.


I found type casting to be helpful when dealing with cypress return HTMLElement