lizlux
lizlux

Reputation: 636

How to work with form elements in typescript

I'd like to access form elements via myForm.elements, and then access each element by it's name, for example, myForm.elements.month. Typescript doesn't like this b/c it doesn't know that form.elements contains a property of month. I thought, let's create an interface! So I did, (see code below), but I'm getting this typescript error: Neither type 'HTMLCollection' nor type 'FormElements' is assignable to the other

Here's the code I'm working with:

interface FormElements {
    day: HTMLInputElement;
    month: HTMLInputElement;
    year: HTMLInputElement;
}

class BirthdateInput {
    constructor(form: HTMLFormElement) {
        var elements: FormElements = <FormElements> form.elements; // error here

        this.day = elements.day;
        this.month = elements.month;
        this.year = elements.year;
    }
}

Any ideas on how to better cast my form.elements object so typescript won't complain?

Upvotes: 12

Views: 28310

Answers (4)

FrameMuse
FrameMuse

Reputation: 373

Origin answer

Create an interface extending HTMLFormControlsCollection or HTMLCollection and add there your inputs

interface FormElements extends HTMLFormControlsCollection {
    day: HTMLInputElement
    month: HTMLInputElement
    year: HTMLInputElement
}

Eventually, for example, getting this

interface FormElements extends HTMLFormControlsCollection {
    day: HTMLInputElement
    month: HTMLInputElement
    year: HTMLInputElement
}
function onSearch(event: FormEvent<HTMLFormElement>) {
    event.preventDefault()
    const elements = event.currentTarget.elements as FormElements

    // ...
  }

However

You can create a helper so you don't need to write an interface for each form every time

type FormElements<U extends string> = HTMLFormControlsCollection & Record<U, HTMLInputElement>

And use it like this

const elements = event.currentTarget.elements as FormElements<"id" | "name" | "type" | "amount">

Eventually, for example, getting this

function onSearch(event: FormEvent<HTMLFormElement>) {
    event.preventDefault()
    const elements = event.currentTarget.elements as FormElements<"id" | "name" | "type" | "amount">

    // ...
  }

Upvotes: 6

Alex Dresko
Alex Dresko

Reputation: 5203

Whether right or wrong, I have found that you can also do something like this:

interface FormElements {
    day: HTMLInputElement;
    month: HTMLInputElement;
    year: HTMLInputElement;
}

class BirthdateInput {
    constructor(form: HTMLFormElement) {
        var elements: FormElements = <FormElements>(<any> form.elements); 

        this.day = elements.day;
        this.month = elements.month;
        this.year = elements.year;
    }
}

Upvotes: 0

lizlux
lizlux

Reputation: 636

Turns out adding an extends clause fixes it:

interface FormElements extends HTMLCollection {
    day: HTMLInputElement;
    month: HTMLInputElement;
    year: HTMLInputElement;
}

Upvotes: 5

Ryan Cavanaugh
Ryan Cavanaugh

Reputation: 221024

Best way would be to write it like this:

// Note 'extends' clause here
interface FormElements extends HTMLFormElement {
    day: HTMLInputElement;
    month: HTMLInputElement;
    year: HTMLInputElement;
}

class BirthdateInput {
    constructor(form: HTMLFormElement) {
        var elements: FormElements = <FormElements> form.elements; // OK
        // ...

Upvotes: 6

Related Questions