Search code examples
typescripttypescript-genericstypescript-typestypescript-class

How can I identify/type guard DOM elements dynamically


Is there a way for me to set a condition that evaluates the type of a variable?

I know I can use as to assert when I call the function to make typescript happy. But that doesn't give me the safety I'm seeking.

I want to type guard to make sure my element is a specific HTMLElement type. I'm trying to build a generic function that wraps around document.querySelector. It would accept a selector string and a Type to check against as arguments.

Attempting =>

I tried passing in the Type as a type argument, but I don't know how to evaluate the type.

function getElement<T extends HTMLElement>(selector: string, type: string): T {
  const el: T | null = document.querySelector(selector);

  if (!el || (typeof el !== T)) {
    throw new Error('Element does not exist');
  }
  return el;
}

const unit: HTMLInputElement = getElement<HTMLInputElement>('#unitToggle', '')
//type argument makes typescript happy but doesn't typeguard.

Next, realizing DOM types are built off the classes, I could use instanceof. So I tried:

function getElement<T extends HTMLElement>(selector: string, type: string): T {
  const el: T | null = document.querySelector(selector);

  if (!el || el instanceof type) {
    throw new Error('Element does not exist');
  }
  return el;
}

const unit: HTMLInputElement = getElement('#unitToggle', 'HTMLInputElement')

But I don't know how to pass a class ?definition? as an argument? How can I dynamically type guard my DOM elements?


Solution

  • In order to use the operator instanceof to validate the selected element, you'll need to require that the caller provides the class of the expected element. You can use the type utility InstanceType<Type> in order to derive the type of element from the class constructor type:

    TS Playground

    function getElement<
      ElConstructor extends new (
        ...params: any[]
      ) => HTMLElement,
    >(selector: string, ctor: ElConstructor): InstanceType<ElConstructor> {
      const el = document.querySelector<InstanceType<ElConstructor>>(selector);
      if (el instanceof ctor) return el;
      throw new Error("Element does not exist");
    }
    
    const unit = getElement("#unitToggle", HTMLInputElement);
    //    ^? const unit: HTMLInputElement