The following code will not do what you want:
class MyElement extends XElement {
static get properties() {
return {
message: { type: String, default: 'default message' },
};
}
static get template(html) {
return ({ message }) {
return html`${message}`;
}
}
}
customElements.define('my-element', MyElement);
/* … */
const element = document.createElement('my-element');
element.render();
assert(element.textContent === 'default message');
You’re meant to actually call something like document.body.append(element) such that connectedCallback is called†. By calling render() prematurely like this, you will get a properties bag which is likely all undefined and importantly you are not guaranteed that initial / default values exist! It’s this last part that is so problematic.
† Yes, you can manually call connectedCallback() to initialize… which is… basically fine, but not something which we say we officially support.
Option 1
I think we should consider throwing hard within render() if we detect that we have not yet been initialized. The element is not meant to be used this way and it’s only really a mistake that you can call render() like this.
Related — if we really wanted to lock this down, we could perform a sanity check within connectedCallback to see if we are indeed attached and again throw hard if we are not (i.e., if an integrator called element.connectedCallback() manually).
Option 2
Give the users what they want… we could initialize from render if we have yet to initialize… I.e., rather than throw — take extra steps to initialize just in time in this case. Users can already call like element.connectedCallback() to achieve this by less-semantic means. The main issue here is that we are not connected… so we cannot really know if we have all the right attributes — but maybe that’s alright (they will eventually become consistent via attributeChangedCallback.
The following code will not do what you want:
You’re meant to actually call something like
document.body.append(element)such thatconnectedCallbackis called†. By callingrender()prematurely like this, you will get apropertiesbag which is likely all undefined and importantly you are not guaranteed thatinitial/defaultvalues exist! It’s this last part that is so problematic.† Yes, you can manually call
connectedCallback()to initialize… which is… basically fine, but not something which we say we officially support.Option 1
I think we should consider throwing hard within
render()if we detect that we have not yet beeninitialized. The element is not meant to be used this way and it’s only really a mistake that you can callrender()like this.Related — if we really wanted to lock this down, we could perform a sanity check within
connectedCallbackto see if we are indeed attached and again throw hard if we are not (i.e., if an integrator calledelement.connectedCallback()manually).Option 2
Give the users what they want… we could initialize from
renderif we have yet to initialize… I.e., rather than throw — take extra steps to initialize just in time in this case. Users can already call likeelement.connectedCallback()to achieve this by less-semantic means. The main issue here is that we are not connected… so we cannot really know if we have all the right attributes — but maybe that’s alright (they will eventually become consistent viaattributeChangedCallback.