so I was looking for a way to setup an ES6 'abstract' base class for custom elements, which self-registers (window.customElements.define) upon the first instantiation of a child.
The approach I came up with is the following:
class MyBase extends HTMLElement {
constructor(tag, child) {
MyBase.register(tag, child);
super();
if(this.constructor.name === MyBase) {
throw new Error('Error: Do not instantiate base class!');
}
}
static register(tag, child) {
if(!window.customElements.get(tag)) {
window.customElements.define(tag, child);
}
}
}
class MyChild extends MyBase {
constructor() {
super(MyChild.Tag(), MyChild);
if(window.customElements.get(MyChild.Tag())) {
console.log(`${this.constructor.name} registered!`)
}
}
static Tag() {
return "my-child";
}
}
const NiceChild = new MyChild();
Is there a way to avoid the statics and the MyBase constructor parameters? I've tried various approaches but I wasn't able to figure out a more elegant solution due to the fact that the registration must be called before the HTMLElement constructor is called. Also, I of course don't want the registration to be performed if MyBase is instantiated directly.
Edit:
I've added another snippet to showcase that the registration in fact happens upon instantiation of MyChild.
class MyBase extends HTMLElement {
constructor(tag, child) {
MyBase.register(tag, child);
super();
if(this.constructor.name === MyBase) {
throw new Error('Error: Do not instantiate base class!');
}
}
static register(tag, child) {
if(!window.customElements.get(tag)) {
window.customElements.define(tag, child);
}
}
}
class MyChild extends MyBase {
constructor() {
super(MyChild.Tag(), MyChild);
}
static Tag() {
return "my-child";
}
}
if(window.customElements.get("my-child")) {
console.log(`Before Constructor: my-child registered!`)
} else {
console.log(`Before Constructor: my-child not registered!`)
}
const NiceChild = new MyChild();
if(window.customElements.get("my-child")) {
console.log(`After Constructor: my-child registered!`)
} else {
console.log(`After Constructor: my-child not registered!`)
}
The constructor is not where you register your custom element: you register it "immediately" with your class declaration, and then if you want your code to start running only after the browser has finished loading that in, wait for it.
And note that, because we can call
customElements.definestatically inside a class, we can also register subclasses by calling the base class'sregisterusing a static assignment (but then you'll want to mark it as a private field because it'll be "useless" to anything else)Also note that you don't want to do all that much of anything in your constructor: not only do you not want to register the custom element until an instance is created, when the constructor runs there isn't even a DOM node to work with yet. That only happens when the element gets inserted into the DOM, at which point the connectedCallback function gets called.