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. :)
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.
If you are using react you can use the as
operator.
let inputValue = (document.getElementById(elementId) as HTMLInputElement).value;
<>
operators to cast, since these are used in React. So you have to use as: basarat.gitbooks.io/typescript/docs/types/…
as
operator is a TypeScript annotation, not a React. Thanks.
(document.getElementById("addUserForm") as HTMLFormElement).reset();
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
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);
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.
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.
.value
on the p element, I am accessing it on the input
element. Or am I mistaken?
document.getElementById("greet")
and you have <p id="greet"></p>
...
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. :)
getElementById
and how do I do that in typescript? Or is there another method than getElementById
that returns HTMLInputElement
?
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);
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 = ''
}
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>
.
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?
getElementById('foo')
and that works, then to translate to querySelector
, you need querySelector('#foo')
with the #
. Then add generics with .querySelector<HTMLElement>('#foo')
If you are using angular you can use -
const element = document.getElementById('elemId') as HTMLInputElement;
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).
This work for me:
let inputValue = (swal.getPopup().querySelector('#inputValue ')as HTMLInputElement).value
swal
even mean?
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.
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.
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.
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.
Success story sharing