Trade-Ideas Philip
Trade-Ideas Philip

Reputation: 1247

Ensure object type in TypeScript

I'm using TypeScript for the browser. I find myself writing a lot of code like this:

const button = document.getElementById(id);
if (!(button instanceof HTMLButtonElement)) {
  throw new Error("TODO -- insert better error message here");
}
button.disabled = false;

The throw is required. getElementById() returns type HTMLElement | null, which does not support the disabled property. After the throw the type correctly changes to HTMLButtonElement. I could have done this with a type assertion, but this code also includes a runtime check.

Is there any way to move this into a function? Something like:

const button = verify(document.getElementById(id), HTMLButtonElement);
button.disabled = false;

or

const button = verify<HTMLButtonElement>(document.getElementById(id));
button.disabled = false;

But not

const button = verify<HTMLButtonElement>(document.getElementById(id), HTMLButtonElement);
button.disabled = false;

because then I'm typing the same word twice and I could easily make a mistake.

In Java or C# I'd say (HTMLButtonElement)document.getElementById(id) instead of verify(). In C++ I'd say dynamic_cast< HTMLButtonElement & >(document.getElementById(id)). Again, I'm trying to do the runtime check and satisfy compiler. And I want to avoid as much typing as possible.

Upvotes: 0

Views: 627

Answers (3)

Trade-Ideas Philip
Trade-Ideas Philip

Reputation: 1247

This is based on the accepted answer.

/**
 * This is a wrapper around document.getElementById().
 * This ensures that we find the element and that it has the right type or it throws an exception.
 * Note that the return type of the function matches the requested type.
 * @param id Look for an element with this id.
 * @param ty This is the type we are expecting.  E.g. HtmlButtonElement
 */
function getById<T extends Element>(id : string, ty: {new(): T}): T {
  const found = document.getElementById(id);
  if (!found) {
    throw new Error("Could not find element with id " + id + ".  Expected type:  " + ty.name);
  }
  if (found instanceof ty) {
    return found;
  } else {
      throw new Error("Element with id " + id + " has type " + found.constructor.name + ".  Expected type:  " + ty.name);
  }
}

Upvotes: 0

Cerberus
Cerberus

Reputation: 10218

This is a little harder then I expected, but it works:

function verify<T extends Element>(element: Element | null, ty: {new(): T}): T {
    if (element === null) {
        throw new Error("TODO: element is null");
    }
    if (element instanceof ty) {
        return element;
    } else {
        throw new Error("TODO: wrong type");
    }
}

Playground

Here's a snippet to check the correctness:

function verify(element, ty) {
  if (element === null) {
    throw new Error("TODO: element is null");
  }
  if (element instanceof ty) {
    return element;
  } else {
    throw new Error("TODO: wrong type");
  }
}

function enableButton(id) {
  const button = verify(document.getElementById(id), HTMLButtonElement);
  button.disabled = false;
}

enableButton("button");
try {
  enableButton("not_exist");
} catch (e) {
  console.error(e);
}
try {
  enableButton("not_a_button");
} catch (e) {
  console.error(e);
}
button:disabled {
  color: grey;
}
<div id="not_a_button"></div>
<button id="button" disabled>Enabled</button>
<button id="disabled_button" disabled>Disabled</button>

Upvotes: 1

Declan Fitzpatrick
Declan Fitzpatrick

Reputation: 160

export function isHTMLButtonElement(value: any): value is HTMLButtonElement
    {
        return value instanceof HTMLButtonElement;
    }

Something like this, the return type specifies that it is a HTMLButtonElement. Code could be nicer but you get the jist :)

Or if you know for sure what you're querying for will be HTMLButtonElement you could just do.

const button = document.getElementById(id) as HTMLButtonElement;

Upvotes: 0

Related Questions