Why is Event.target not Element in Typescript?

TypescriptEvent Listener

Typescript Problem Overview


I simply want to do this with my KeyboardEvent

var tag = evt.target.tagName.toLowerCase();

While Event.target is of type EventTarget, it does not inherit from Element. So I have to cast it like this:

var tag = (<Element>evt.target).tagName.toLowerCase();

This is probably due to some browsers not following standards, right? What is the correct browser-agnostic implementation in TypeScript?

P.S. I am using jQuery to capture the KeyboardEvent.

Typescript Solutions


Solution 1 - Typescript

It doesn't inherit from Element because not all event targets are elements.

From MDN:

> Element, document, and window are the most common event targets, but other objects can be event targets too, for example XMLHttpRequest, AudioNode, AudioContext, and others.

Even the KeyboardEvent you're trying to use can occur on a DOM element or on the window object (and theoretically on other things), so right there it wouldn't make sense for evt.target to be defined as an Element.

If it is an event on a DOM element, then I would say that you can safely assume evt.target. is an Element. I don't think this is an matter of cross-browser behavior. Merely that EventTarget is a more abstract interface than Element.

Further reading: https://github.com/Microsoft/TypeScript/issues/29540

Solution 2 - Typescript

JLRishe's answer is correct, so I simply use this in my event handler:

if (event.target instanceof Element) { /*...*/ }

Solution 3 - Typescript

Using typescript, I use a custom interface that only applies to my function. Example use case.

  handleChange(event: { target: HTMLInputElement; }) {
    this.setState({ value: event.target.value });
  }

In this case, the handleChange will receive an object with target field that is of type HTMLInputElement.

Later in my code I can use

<input type='text' value={this.state.value} onChange={this.handleChange} />

A cleaner approach would be to put the interface to a separate file.

interface HandleNameChangeInterface {
  target: HTMLInputElement;
}

then later use the following function definition:

  handleChange(event: HandleNameChangeInterface) {
    this.setState({ value: event.target.value });
  }

In my usecase, it's expressly defined that the only caller to handleChange is an HTML element type of input text.

Solution 4 - Typescript

Typescript 3.2.4

For retrieving property you must cast target to appropriate data type:

e => console.log((e.target as Element).id)

Solution 5 - Typescript

Could you create your own generic interface that extends Event. Something like this?

interface DOMEvent<T extends EventTarget> extends Event {
  readonly target: T
}

Then you can use it like:

handleChange(event: DOMEvent<HTMLInputElement>) {
  this.setState({ value: event.target.value });
}

Solution 6 - Typescript

With typescript we can leverage type aliases, like so:

type KeyboardEvent = {
  target: HTMLInputElement,
  key: string,
};
const onKeyPress = (e: KeyboardEvent) => {
  if ('Enter' === e.key) { // Enter keyboard was pressed!
    submit(e.target.value);
    e.target.value = '';
    return;
  }
  // continue handle onKeyPress input events...
};

Solution 7 - Typescript

I use this:

onClick({ target }: MouseEvent) => {
    const targetDivElement: HTMLDivElement = target as HTMLDivElement;
    
    const listFullHeight: number = targetDivElement.scrollHeight;
    const listVisibleHeight: number = targetDivElement.offsetHeight;
    const listTopScroll: number = targetDivElement.scrollTop;
    }

Solution 8 - Typescript

@Bangonkali provide the right answer, but this syntax seems more readable and just nicer to me:

eventChange($event: KeyboardEvent): void {
    (<HTMLInputElement>$event.target).value;
}

Solution 9 - Typescript

I'm usually facing this problem when dealing with events from an input field, like key up. But remember that the event could stem from anywhere, e.g. from a keyup listener on document, where there is no associated value. So in order to correctly provide the information I'd provide an additional type:

interface KeyboardEventOnInputField extends KeyboardEvent {
  target: HTMLInputElement;
}
...

  onKeyUp(e: KeyboardEventOnInputField) {
    const inputValue = e.target.value;
    ...
  }

If the input to the function has a type of Event, you might need to tell typescript what it actually is:

  onKeyUp(e: Event) {
    const evt = e as KeyboardEventOnInputField;
    const inputValue = evt.target.value;
    this.inputValue.next(inputValue);
  }

This is for example required in Angular.

Solution 10 - Typescript

For Angular 10+ Users

Just declare the HTML Input Element and extend it to use the target as an object as I did below for my bootstrap 4+ file browser input. This way you can save a lot of work.

  selectFile(event: HTMLInputElement & { target: HTMLInputElement}) {
    console.log(event.target.files);
    this.selectedFile = event.target.files[0];
  }

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
Questiondaniel.sedlacekView Question on Stackoverflow
Solution 1 - TypescriptJLRisheView Answer on Stackoverflow
Solution 2 - TypescriptSimon EpskampView Answer on Stackoverflow
Solution 3 - TypescriptBangonkaliView Answer on Stackoverflow
Solution 4 - TypescriptdimpiaxView Answer on Stackoverflow
Solution 5 - TypescriptWickyNilliamsView Answer on Stackoverflow
Solution 6 - TypescriptMaksim KostrominView Answer on Stackoverflow
Solution 7 - TypescriptGennady MagomaevView Answer on Stackoverflow
Solution 8 - TypescripthovadoView Answer on Stackoverflow
Solution 9 - TypescriptberslingView Answer on Stackoverflow
Solution 10 - TypescriptJorge Uriel Lopez Diaz de LeonView Answer on Stackoverflow