Reputation: 30138
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. :)
Upvotes: 492
Views: 591182
Reputation: 127
You can use util functions with and without type as parameter, like generics in Java:
function elem(id:string){
let elementById = document.getElementById(id);
if(elementById==null) throw new Error('Not found HTMLElement by id: '+id);
return elementById;
}
function elemType(id:string, type: any): typeof type {
return elem(id) as typeof type;
}
elemType('myInput', HTMLInputElement).value = "Hello World!";
elemType('myButton', HTMLButtonElement).disabled = true;
Upvotes: 0
Reputation: 577
Surprisingly, nobody has yet pointed out that you may need to add "DOM" as a lib in your tsconfig file.
"lib": ["es2022", "DOM"],
Upvotes: 1
Reputation: 2814
const handleMyEvent = (event : React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
const input = (event.target as HTMLInputElement).value;
console.log('handleMyEvent input -> ', input)
}
Upvotes: 4
Reputation: 1942
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'.
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."?
Upvotes: 0
Reputation: 2058
const a = document.getElementById("a")
if (a instanceof HTMLInputElement) {
// a.value is valid here
console.log(a.value)
}
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
.
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.
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.
// <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
// <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)
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).
Upvotes: 13
Reputation: 30138
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.
Upvotes: 817
Reputation: 645
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 = ''
}
Upvotes: 17
Reputation: 41
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.
Upvotes: 1
Reputation: 8152
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.
Upvotes: 0
Reputation: 371198
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>
.
Upvotes: 10
Reputation: 10833
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
Upvotes: 53
Reputation: 304
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.
Upvotes: 0
Reputation: 19
This work for me:
let inputValue = (swal.getPopup().querySelector('#inputValue ')as HTMLInputElement).value
Upvotes: 1
Reputation: 113
If you are using angular you can use -
const element = document.getElementById('elemId') as HTMLInputElement;
Upvotes: 7
Reputation: 189
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);
Upvotes: 15
Reputation: 2955
If you are using react you can use the as
operator.
let inputValue = (document.getElementById(elementId) as HTMLInputElement).value;
Upvotes: 142
Reputation: 406
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.
Upvotes: -1
Reputation: 815
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);
Upvotes: 50
Reputation: 1277
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.
Upvotes: 28
Reputation: 340993
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.
Upvotes: 21