So, in a library that I'm creating that uses custom elements, you obviously need to define the class in the CustomElementsRegistry
before you may instantiate it.
As of right now, this is being solved with a decorator:
class Component extends HTMLElement {
static register (componentName) {
return component => {
window.customElements.define(componentName, component);
return component;
}
}
}
@Component.register('my-element')
class MyElement extends Component { }
document.body.appendChild(new MyElement());
This works, however, I would like to automatically register the custom element upon instantiation of the class (so that the author does not have to add the decorator to every single component that they write). This could maybe be accomplished via a Proxy
.
My problem, though, is that when I attempt to use a Proxy on the constructor, and attempt to return an instance of the target, I still get Illegal Constructor
, as if the element has never been defined in the registry.
This obviously has to do with the way I am instantiating the class inside of the proxy, but I'm unsure of how to do it otherwise. My code is as follows:
Please run in latest Chrome:
class Component extends HTMLElement {
static get componentName () {
return this.name.replace(/[A-Z]/g, char => `-${ char.toLowerCase() }`).substring(1);
}
}
const ProxiedComponent = new Proxy(Component, {
construct (target, args, extender) {
const { componentName } = extender;
if (!window.customElements.get(componentName)) {
window.customElements.define(componentName, extender);
}
return new target(); // culprit
}
});
class MyElement extends ProxiedComponent { }
document.body.appendChild(new MyElement());
How can I continue the chain of inheritance inside of the proxy without losing the context of the fact that I'm instantiating the MyElement
class so that it doesn't throw the Illegal Constructor
exception?
There were 2 problems:
new target()
created LibElement
instance, that is not registered as custom element. And here you got Illegal Constructor
error.LibElement
resulting DOM element will be <lib-element>
, cause you call new target
and at this point javascript have no idea about child class.The only way i found is to use Reflect
API to create right instance of object.
class LibElement extends HTMLElement {
static get componentName () {
return this.name.replace(/[A-Z]/g, char => `-${ char.toLowerCase() }`).substring(1);
}
}
const LibElementProxy = new Proxy(LibElement, {
construct (base, args, extended) {
if (!customElements.get(extended.componentName)) {
customElements.define(extended.componentName, extended);
}
return Reflect.construct(base, args, extended);
}
});
class MyCustomComponent extends LibElementProxy {}
class MyCustomComponentExtended extends MyCustomComponent {}
document.body.appendChild(new MyCustomComponent());
document.body.appendChild(new MyCustomComponentExtended());
And I really liked this idea of proxied constructor for auto-registration of custom elements )