When I write "document.querySelector().
" and click CTRL+Spacebar to trigger suggestions, the style
property does not get suggested, but it seems to work fine with document.getElementById()
.
Why is this happening and how do I fix this?
With querySelector
:
With getElementById
:
I expected the IntelliSense to suggest the 'style' property with the 'document.querySelector().' but it did not. Why?
If you're in a hurry and don't care about the "why", skip to the end of this post for the "solution"
This is interesting. According to the DOM standard, both querySelector
and getElementById
return either an Element
instance, or null
.
Docs:
However, in lib.dom.ts, querySelector
is typed to return Element | null
, and getElementById
for HTML documents is typed to return HTMLElement | null
. And the style
DOM property is defined on HTMLElement
, but not on Element
. There some nuance in that the typings for querySelector
also have overloads that detect selectors that are simply standard tag names in order to specialize those cases and return the corresponding type.
This was already brought up in the TypeScript GitHub repo's issue tracker here: Document.getElementById()
must return Element
, not HTMLElement
#19549, which was closed to move the discussion of that issue to Node.parentElement should be Element, not HTMLElement #4689. You can follow the discussion there. Here are some quotes of the maintainers' thoughts on the matter:
This is arguable, because there was complainant before saying that it was too cumbersome to always cast the type
Element
toHTMLElement
when in most common cases the actual type isHTMLElement
, even though the spec says it should beElement
.For example, the return type of
getElementById
is defined asElement
, however we made itHTMLElement
to avoid too much casting. If the type is some otherElement
, you can just cast it toElement
first and then cast it again to the final type. I think in most cases theparentElement
isHTMLElement
too, therefore it might be better just leave it the way it is.- zhengbli
@jun-sheaf sent a PR for these changes and I've been reviewing about whether it should make it in for 4.1.
I don't think we should make these changes, because it's going to add break a lot of code in a way that people won't think is to their advantage. TS tries to balance correctness and productivity and I think making
getElementById
start returning code which needs to be casted to get the same tooling support we have today in the majority of cases (e.g. HTML nodes in a JS context) is going to be a bad call for TypeScript.[...]
Switching it to
getElementById<E extends Element = HTMLElement>(elementId: string): E | null
on the other hand still allows for setting the return type less drastic ways for the uncommon cases:const logo2 = getElementById2("my-logo") as SVGPathElement const logo3 = getElementById2<SVGPathElement>("my-logo")
But doesn't hinder the default JS tooling support.
- orta
That issue was closed as completed by this commit: d561e08
, which basically did what orta was talking about for getElementById
. I.e. it still returns HTMLElement
for HTML documents (but not other types of documents like SVG or XML) if I understand correctly.
In other words, things are functioning as the TypeScript maintainers have written them to function.
If you want the style
property to available on the result of querySelector
, then type-cast it to an HTMLElement
using a type assertion (with as HTMLElement
), or with a JSDoc annotation (with /**@type{HTMLElement}*/const e = document.querySelector(...); e.style;
), or do a runtime type check with if (foo instanceof HTML...Element) {...}
, or use the generic parameter of querySelector
.