451

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. :)

Mosh Feu
  • 28,354
  • 16
  • 88
  • 135
Bjarke Freund-Hansen
  • 28,728
  • 25
  • 92
  • 135

18 Answers18

751

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.

Kayce Basques
  • 23,849
  • 11
  • 86
  • 120
Bjarke Freund-Hansen
  • 28,728
  • 25
  • 92
  • 135
  • 1
    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. – Pardeep Jain Jan 16 '16 at 08:21
  • 1
    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. – Turtles Are Cute Mar 24 '17 at 23:36
  • @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. – Michaël Polla May 29 '18 at 12:39
  • In my case, I had to change it to HTMLAnchorElement. – Wildhammer Jul 08 '19 at 18:30
  • 3
    @Wildhammer Because your element _wasn't_ an Input element! The OP's is. – Auspex Aug 26 '19 at 15:03
  • Is it possible to disable this type or error in the tsconfig? – basickarl Nov 17 '20 at 15:56
  • How do I resolve the warning when using event in an event handler function? Like `const handleSearch = (e) => { const searchValue = e.value; }`, where `value` is an attribute on the DOM element `handleSearch` was called on? – darKnight Feb 18 '21 at 12:08
  • for me with typescript it worked => const filtroValue = (document.getElementById('filtro') as HTMLInputElement).value – Sarah Sep 24 '21 at 22:36
  • 1
    This answer saved me form getting fired – JobHunter69 May 14 '22 at 21:42
  • Adding "any" type to this variable fixed my issue. `var inputValue: any = element;` – Mithun Jack Aug 08 '22 at 05:47
  • Very helpful answer, tx! Maybe overkill, but `getElementById` actually returns a type of `HTMLElement | null`. I think the cast should really be `as HTMLInputElement | null`, otherwise you loose the null typecheck (I'm surprised Typescript doesn't complain without the `| null`). If you add this, you can then check for a null element `if (fooElement !== null) { fooElement!.value = 'bar' }`. Note the `!` after `fooElement`: you _may_ need this to tell Typescript you've checked that `fooElement` is not null. – Hawkeye Parker Jan 12 '23 at 20:38
138

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

let inputValue = (document.getElementById(elementId) as HTMLInputElement).value;
Michael
  • 2,825
  • 3
  • 24
  • 30
  • 7
    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. – NigelTufnel Sep 26 '18 at 14:02
  • 7
    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: https://basarat.gitbooks.io/typescript/docs/types/type-assertion.html#as-foo-vs-foo – Powderham Apr 01 '19 at 15:41
  • Although this didnt work for me, it did direct me the right direction :) up vote!!! – Simon Price May 22 '19 at 06:39
  • 3
    I'm pretty sure the `as` operator is a TypeScript annotation, not a React. Thanks. – Peter Boomsma Aug 12 '20 at 14:56
  • This worked well for me as I was trying to reset a form `(document.getElementById("addUserForm") as HTMLFormElement).reset();` – AdamVanBuskirk Jun 16 '21 at 23:32
53

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
Leo
  • 10,407
  • 3
  • 45
  • 62
50

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);
woodysan
  • 815
  • 6
  • 7
28

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.

inDream
  • 1,277
  • 10
  • 12
21

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.

Tomasz Nurkiewicz
  • 334,321
  • 69
  • 703
  • 674
  • But I am not accessing `.value` on the p element, I am accessing it on the `input` element. Or am I mistaken? – Bjarke Freund-Hansen Oct 20 '12 at 15:20
  • 1
    @bjarkef: you are calling `document.getElementById("greet")` and you have `

    `...
    – Tomasz Nurkiewicz Oct 20 '12 at 15:30
  • 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. :) – Bjarke Freund-Hansen Oct 20 '12 at 15:34
  • @bjarkef: sorry, you were right. I have rewritten my answer, have a look! – Tomasz Nurkiewicz Oct 20 '12 at 15:39
  • 1
    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` ? – Bjarke Freund-Hansen Oct 20 '12 at 15:44
  • you cant argue the same way for getAttribute, which throws also this error. – The Fool Apr 24 '20 at 16:33
16

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 = ''
}
emmaakachukwu
  • 515
  • 1
  • 7
  • 16
15

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);
KidKode
  • 179
  • 3
  • 11
11
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.

  1. 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
  1. 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)
  1. 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).

touchmarine
  • 1,548
  • 9
  • 11
  • On second thought, `instanceof` does not work for my case. I want to directly use `value` but `if (value instanceof number) {this.valueProperty = value}` gives `TS2693` but `if (value instanceof Number) {this.valueProperty = value}` gives the error: `TS2322` The solution for my case is: `if (typeof value === 'number') {this.valueProperty = value}`. – ThaJay Sep 23 '22 at 14:00
  • `const a = document.getElementById("a") as HTMLInputElement` this line itself was helpful for me – Abhi Apr 06 '23 at 15:26
10

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>.

CertainPerformance
  • 356,069
  • 52
  • 309
  • 320
  • 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? – Timo Mar 22 '22 at 21:53
  • 1
    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('#foo')` – CertainPerformance Mar 22 '22 at 23:30
7

If you are using angular you can use -

const element = document.getElementById('elemId') as HTMLInputElement;
Rohit Grover
  • 113
  • 2
  • 7
2
  const handleMyEvent = (event : React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
    
    const input = (event.target as HTMLInputElement).value;
    console.log('handleMyEvent input -> ', input)

  }
jonathasborges1
  • 2,351
  • 2
  • 10
  • 17
1

This work for me:

let inputValue = (swal.getPopup().querySelector('#inputValue ')as HTMLInputElement).value
Dharman
  • 30,962
  • 25
  • 85
  • 135
Juan Pablo
  • 19
  • 3
1

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.

Stebenwolf
  • 31
  • 7
0

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.

JaMondo
  • 278
  • 2
  • 8
0

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.

jfunk
  • 7,176
  • 4
  • 37
  • 38
0

My original code:

<ion-input
  type="number"
  value={{propertyValue}}
  (change)="propertyValue = $event.target.value"
></ion-input>

makes my editor show the following error:

Property 'value' does not exist on type 'EventTarget'. ngtsc(2339)

Unfortunately the only way I have found to solve this in the template is to use $any().

I agree with this answer on here of touchmarine where he advocates using a type guard instead of type assertion.

I want to directly use value in my callback. value got typed as string | number which may be correct but but this.propertyValue is number so I still can not set it directly:

(property) Components.IonInput["value"]?: string | number

Using instanceof like in the mentioned answer with number errors like this:

TS2693: 'number' only refers to a type, but is being used as a value here.

And with Number errors like this (notice the capital N):

TS2322: Type 'Number' is not assignable to type 'number'.

The solution

  • Casting types is not recommended
  • we need a callback because this is not supported in the template
  • The #id selector is the preferred way to get a value from an element in Angular nowadays. It gets the typing most of the way there.
  • instanceof does not work for primitives so we use typeof
<ion-input
  type="number"
  value={{propertyValue}}
  #propertyValueInput
  (change)="setPropertyValue(propertyValueInput.value)"
></ion-input>
public setPropertyValue (value) {
  if (typeof value === 'number') {this.propertyValue = value}
}

Finally! We are type safe and error free!

P.S. This has the added benefit of just ignoring the user input if somehow anything else than a number gets put in. For example, in the browser I can just as well type letters in that input field.

more info for reference: Why does 'instanceof' in TypeScript give me the error "'Foo' only refers to a type, but is being used as a value here."?

ThaJay
  • 1,758
  • 1
  • 19
  • 30
-1

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.

Stefano Zanini
  • 5,876
  • 2
  • 13
  • 33
Alok Adhao
  • 447
  • 5
  • 6