Accommodating or sidestep dataset and setAttribute typings / purpose of optional typing

38 views Asked by At

Q: Is there a way to explicitly state, globally, perhaps through a .tds, that in the case of HTMLElement.dataset and setAttribute() that implicit casts from Any to string are acceptable, or that these members accept Any to begin with? I am not interested in flagging each case.

Context: I'm new to TS and would like to experiment with an existing project/experiment/prototype that grew big enough to warrant some typing, but that makes extensive use of dataset and setAttribute.

I do not care that my above question contains a bad idea©. Typescript linting has provided me with immediate benefits by catching unintentional things... but only when I can distinguish these cases from lots of instances of two things done on purpose.

However, I am making an assumption that, in general, one human purpose of optional typing among others, philosophically speaking, is to allow a developer to distinguish between bad ideas and accidents. If I'm wrong about that, then I would like to know.

1

There are 1 answers

1
jcalz On BEST ANSWER

Sure, if you'd like to gradually type your code and (hopefully temporarily) loosen some of the restrictions you can use your own libraries instead of the built-in ones. Your compiler options probably specify a value for lib or use the default for your target which includes a reference to the built-in lib.dom.d.ts declaration file. You don't have to use that, so you can choose to make a copy of it and alter it to your heart's content.

In this case, though, since the built-in HTMLElement interface doesn't directly contain the setAttribute() method (which is defined in the Element interface) or the dataset property (which is defined in the HTMLOrSVGElement interface, you can use declaration merging to add your own version of these into HTMLElement:

// merge into existing HTMLElement interface
interface HTMLElement {
  setAttribute(qualifiedName: any, value: any): void;
  readonly dataset: { [k: string]: any };
}

(Note: the above only works if your code is already in the global scope. If you are writing code within a module, you might need to use global augmentation via the declare global syntax.)

And use it:

function useHTMLElement(x: HTMLElement) {
  x.innerHTML; // okay

  x.setAttribute(123, "hello"); // okay now
  x.dataset.foo = 123; // okay now

  x.getAttribute(123); // error, didn't override HTMLElement.getAttribute
}

As you can see, the existing HTMLElement definition is there (since innerHTML is known to be a property) but your merged method and properties are working as expected (they do not complain about 123 not being a string).

Okay, hope that helps. As I said, hopefully you will consider altering your code at some point to make it conform to the string constraint that everyone else expects, especially if the code is going to be used or maintained by others and is not just a personal or toy project. But that's up to you and is explicitly not what you care about, so I'll leave it at that.

Good luck!

Link to code