Most people are unaware of the rules of a constructor for Web Components:
Here are the official docs:
https://w3c.github.io/webcomponents/spec/custom/#custom-element-conformance
The summary is:
Your constructor code:
- must have a parameter-less call to super() as first statement in constructor.
- must not have a return statement anywhere in constructor.
- must not call document.write() or document.open().
- must not inspect element's attributes.
- must not change or add any attributes or children.
In general, the constructor should be used to set up initial state and default values, and to set up event listeners and possibly a shadow root.
In general I agree with @T.J. Crowder, but I would make one minor modification to the Bar
object:
class Bar extends HTMLElement {
constructor() {
super();
this.attachShadow({mode: 'open'});
}
static get observedAttributes() {
// Indicate that we want to be notified
// when the `val` attribute is changed
return ['val'];
}
connectedCallback() {
// Render the initial value
// when this element is placed into the DOM
render(this.getAttribute('val'));
}
attributeChangedCallback(attrName, oldVal, newVal) {
if (oldVal != newVal) {
// If the value for the `val` attribute has changed
// then re-render this element
render(newVal);
}
}
render(val = 'no value') {
this.shadowRoot.innerHTML = `
<div class='bar'>
<span>${val}</span>
</div>
`;
}
}
customElements.define('x-bar', Bar);
This used the standard of attributeChangedCallback
and observedAttributes
. While overriding the setAttribute
functionality works it is not future proof. If the API for setAttribute
were to change, in the future, then you would need to remember to fix your component. And doing this with many components racks up a lot of developer debt.
class Bar extends HTMLElement {
constructor() {
super();
this.attachShadow({mode: 'open'});
// Render the blank DOM
this.shadowRoot.innerHTML = '<div class="bar"><span>no value</span><div>';
this._span = this.shadowRoot.querySelector('span');
}
static get observedAttributes() {
// Indicate that we want to be notified
// when the `val` attribute is changed
return ['val'];
}
attributeChangedCallback(attrName, oldVal, newVal) {
if (oldVal != newVal) {
// If the value for the `val` attribute has changed
// then insert the value into the `<span>`
this._span.textContent = newVal || 'no value';
// OR: this._span.innerHTML = newVal || 'no value';
// But make sure someone has not tried to hit you
// with a script attack.
}
}
get val() {
return this._span.textContent;
}
set val(newVal) {
if (newVal == null || newVal === false || newVal === '') {
this.removeAttribute('val');
}
else {
this.setAttribute('val', newVal);
}
}
}
customElements.define('x-bar', Bar);
This second method done not re-render the entire DOM it simple inserts the modified attribute value into the <span>
tag.
It also provides properties so you can set the value through the attribute as well as through JavaScript:
var el = document.querySelector('x-bar');
if (el) {
el.val = "A New String";
setTimeout(()=>el.val = '';,2000);
}