When is a Custom Element constructor invoke? (HTMLTemplateElement.content problem)
Take a look at this simple example (don't bother with this, go to the EDIT)
class MyElement extends HTMLElement {
customProperty = "something";
constructor() {
super();
console.log("My Element Constructor");
}
}
customElements.define("my-element", MyElement);
document.body.innerHTML = "<my-element></my-element>";
var myElement = document.querySelector("my-element");
console.log(myElement); //
console.log(myElement.customProperty); //
The output:
<my-element>
undefined
My Element Constructor
The custom element constructor it is not called (but the HTMLElement constructor it is) until the main call stack is done. Is this an expected behavior or a bug?
Thanks!
EDIT
In an attempt to simplify my real case, I proposed the previous example, which I now see is not appropriate to illustrate my problem (the example does work correctly). Thanks to @connexo's answer I was able to isolate the problem (it's a complex project with many dependencies) and translate it to the following example:
class SubElement extends HTMLElement {
constructor() {
super();
console.log("Sub Element Constructor");
}
}
customElements.define("sub-element", SubElement);
class MainElement extends HTMLElement {
constructor() {
super();
this.attachShadow({mode: "open"});
let template = document.createElement("template");
template.innerHTML = "<sub-element></sub-element>";
this.shadowRoot.appendChild(template.content);
// The above line doesn't call the Sub Element constructor
// But the bellow line does it
// this.shadowRoot.innerHTML = "<sub-element></sub-element>";
console.log("Main Element Constructor");
}
}
customElements.define("main-element", MainElement);
var myElement = document.createElement("main-element");
// Sub Component constructor will not be called until
// the parent element is added to the DOM (next line)
// document.body.appendChild(myElement);
For some reason, when the content of a template element is added to a shadowRoot, these elements will not be parsed until the shadowRoot is added to the DOM. But when the innerHTML of the shadowRoot is modified directly, this content is parsed even when the shadowRoot does not belong to the DOM.
Thank you for your time.
Solution 1:
Q: When is a Custom Element constructor invoked?
A: In three situations:
-
If your element is registered in the custom elements registry before the element gets parsed, its constructor is called when parsing the
<my-element
part of the HTML. Notice the missing>
- for this case this is all the parser has parsed when calling the constructor - no attributes, no children). -
If your element is not yet registered in the custom elements registry before the element gets parsed, the constructor gets called right after your custom element registration is done. This is called the upgrade case. Before the upgrade, your custom element to the browser is just an
HTMLUnknownElement
. -
The constructor is also called when you dynamically create your custom element using either
new MyElement()
ordocument.createElement('my-element')
. Notice that a) in both cases neither attributes nor children exist and b) when creating the element usingdocument.createElement('my-element')
the point in time of calling the constructor will be either 1. or 2. (depending on if the custom element has already been registered or not).
Your code will work as expected as long as you make sure it doesn't run before document.body
is available.
The easiest way to achieve this is to include your script only immediately before the closing </body>
tag.
An easy alternative, if your JS is in an external file, is to include it using the boolean attribute defer
:
<script src="./path/to/my/script.js" defer></script>
A 2nd alternative is to wrap your code in a DOMContentLoaded
listener (this way it doesn't matter where you put the script
tag and you also don't need a defer
attribute):
document.addEventListener('DOMContentLoaded', function() {
class MyElement extends HTMLElement {
customProperty = "something";
constructor() {
super();
console.log("My Element Constructor");
}
}
customElements.define("my-element", MyElement);
document.body.innerHTML = "<my-element></my-element>";
var myElement = document.querySelector("my-element");
console.log(myElement); //
console.log(myElement.customProperty); //
});
All three methods basically enforce the upgrade case (2.) which in years of professional practice with authoring large web component libraries has proven to be both the most reliable and the most simple approach.
Edit
Since you've now totally changed your question, here's the answer to that:
The
<template>
HTML element is a mechanism for holding HTML that is not to be rendered immediately when a page is loaded but may be instantiated subsequently during runtime using JavaScript.
Think of a template as a content fragment that is being stored for subsequent use in the document. While the parser does process the contents of the
<template>
element while loading the page, it does so only to ensure that those contents are valid; the element's contents are not rendered, however.
https://developer.mozilla.org/en-US/docs/Web/HTML/Element/template
and
However, the
HTMLTemplateElement
has a content property, which is a read-onlyDocumentFragment
containing the DOM subtree which the template represents. Note that directly using the value of the content could lead to unexpected behavior, see Avoiding DocumentFragment pitfall section below.
https://developer.mozilla.org/en-US/docs/Web/HTML/Element/template#attributes