How can I declare in TypeScript: In this class, any property beginning with '$' is a reference to an Element?
I have a Custom Element class method that automatically stores any child element having attribute data-reference
in a property named after its value prefixed by '$'.
It works perfectly in vanilla JavaScript :
class SomeComponent extends HTMLElement {
connectedCallback() {
this.setReferences()
console.log(this.$myRef) // <div data-reference="myRef"></div>
}
setReferences() {
const referencedElements = this.querySelectorAll('[data-reference]')
referencedElements.forEach((element) => {
const referenceName = '$' + element.getAttribute('data-reference')
this[referenceName] = element
})
}
}
customElements.define('some-component', SomeComponent)
<some-component>
<div data-reference="myRef"></div>
</some-component>
But this won't compile in TypeScript, who doesn't know about $myRef
property:
console.log(this.$myRef) // Property '$myRef' does not exist on type 'SomeComponent'
^^^^^^
So far I've come up with 3 workarounds but none of them is fully satisfying.
Nope!
console.log((this as any).$myRef) // <div data-reference="myRef"></div>
The point is to provide a quick access to any $ref
, so adding as any
or <any>
(plus parenthesis) is not a viable solution.
Probably the cleanest, but not exactly what I want to achieve.
type ElementReferenceMap = { [key: string]: Element }
class SomeComponent extends HTMLElement {
ref: ElementReferenceMap = {};
setReferences() {
const referencedElements = this.querySelectorAll('[data-reference]');
referencedElements.forEach((element) => {
const referenceName = element.getAttribute('data-reference');
this.ref[referenceName] = element as Element;
})
console.log(this.ref.myRef); // <div data-reference="myRef"></div>
}
}
By renaming it to this.$.myRef
I get closer, but it's still not equivalent to the vanilla JavaScript version.
Works as expected (like the JavaScript version), but it doesn't feel right, as it allows any property value.
class SomeComponent extends HTMLElement {
// [x: string]: Element; // conflict with other properties
[x: string]: any; // ok
setReferences() {
const referencedElements = this.querySelectorAll('[data-reference]');
referencedElements.forEach((element) => {
const referenceName = '$' + element.getAttribute('data-reference');
this[referenceName] = element;
})
console.log(this.$myRef); // <div data-reference="myRef"></div>
}
}
I may be missing the obvious solution since I'm quite new to TypeScript. Thanks in advance!
There is no way typescript can generate static checks on your html code, it just can't given html being dynamic (you can dynamically generate DOM with data-reference
attributes) 2 or 3 both are viable options. Just think about this: typescript checks types during compile time, before delivery, when HTML can be populated with extra elements later, during runtime.
If you know your elements beforehand, you could provide static type info for typescript. For example for a (3) option:
class SomeComponent extends HTMLElement {
[x: '$myRef' | '$myOtherRef']: HTMLElement; // ok
....
Or you could just have a plain property:
class SomeComponent extends HTMLElement {
$myProp: HTMLElement
....