I'm testing the limits of the JavaScript Web Components API and I encountered an interesting issue.
I have the following wrapper around an HTMLElement object:
export class Wrapper {
public el: HTMLElement;
constructor(elementName: string) {
this.el = document.createElement(elementName);
customElements.get(elementName) ||
customElements.define(elementName, HTMLElement);
}
}
I use this implementation because HTMLElement has a series of limitations I want to bypass, e.g. the constructor must be empty.
Then, I instanciate the wrapper and append the actual element to the DOM:
let wrapper = new Wrapper("magic-component")
document.body.appendChild(wrapper.el);
To my surprise, this throws the following error:
Uncaught TypeError: Illegal constructor
It appears this error happens when a custom element is created via new
before being defined via customElements.define()
. However, this is not the case here: this.el is regularly created via document.createElement()
and then defined via customElements.define()
. Only the wrapper is created via new
.
class Wrapper {
constructor(elementName) {
this.el = document.createElement(elementName);
customElements.get(elementName) ||
customElements.define(elementName, HTMLElement);
}
}
let wrapper = new Wrapper("magic-component")
document.body.appendChild(wrapper.el);
I am not sure what magic you are after.
With customElements.define
you need to properly extend HTMLElement
class Wrapper {
constructor(elementName) {
customElements.get(elementName) ||
customElements.define(elementName, class extends HTMLElement {
connectedCallback() {
this.innerHTML = elementName
}
});
return document.createElement(elementName);
}
}
let wrapper = new Wrapper("magic-component")
document.body.append(wrapper);
But why the, kinda oldskool, new Wrapper
?
function newElement(elementName) {
customElements.get(elementName) ||
customElements.define(elementName, class extends HTMLElement {
connectedCallback() {
this.innerHTML = elementName
}
});
return document.createElement(elementName);
}
document.body.append(newElement("magic-component"));
Note: once magic-component
exists the CustomElementsRegistry; you can also do:
customElements.define(
"another-element",
class extends customElements.get("magic-component") {
connectedCallback() {
this.innerHTML = this.nodeName; // ANOTHER-ELEMENT
}
});
If you are after mixins, see: https://component.kitchen/elix/mixins