The Elm guide section about using custom elements for JavaScript interoperability has the following custom element example:
customElements.define('intl-date',
class extends HTMLElement {
// things required by Custom Elements
constructor() { super(); }
connectedCallback() { this.setTextContent(); }
attributeChangedCallback() { this.setTextContent(); }
static get observedAttributes() { return ['lang','year','month']; }
// Our function to set the textContent based on attributes.
setTextContent() {
const lang = this.getAttribute('lang');
const year = this.getAttribute('year');
const month = this.getAttribute('month');
this.textContent = localizeDate(lang, year, month);
}
}
);
I could not understand why my custom elements written this way were rendered multiple times
The reason is that attributeChangedCallback()
is called for every observed attribute at initialization time before connectedCallback()
finally attaches the custom element to the DOM.
In the example above, with 3 observed attributes, it means setTextContent()
is called 4 times total! Just add a console.log()
to believe it.
Here is the code I use now to prevent this redundant rendering:
customElements.define('intl-date',
class extends HTMLElement {
#isConnected = false
connectedCallback() {
this.#setTextContent();
this.#isConnected = true;
}
attributeChangedCallback() {
if (this.#isConnected) this.#setTextContent();
}
static observedAttributes = ['lang','year','month'];
#setTextContent() {
const lang = this.getAttribute('lang');
const year = this.getAttribute('year');
const month = this.getAttribute('month');
this.textContent = localizeDate(lang, year, month);
}
}
);
You’ll noticed a few other optimizations:
- I’m not using
constructor()
along withsuper()
as I don’t want to modify the constructor inherited fromHTMLElement
. And it’s working fine without it. - I’m using a static field instead of a getter for
observedAttributes
: after the release of Safari 14.1 in May 2021 static class fields are available in all major browsers. - I’m using #-prefixed private fields so they can be safely mangled by Terser or any other JS minifier.
Don’t you think the docs should be updated to prevent this weird multiple rendering?