I building a web-component and within its constructor using template to initialize its substructure. A part of this substructure is another web-component whose setter method I wish to call. I get the sub-component by querying DOM tree that was created via the template, but through this only standard Element
properties are accessible.
It is a bit of a complex problem and it might be I am missing something fundamental. It seems to be related with the fact that one web component uses another web component via the template clone. It was suggested in this question that the problem might be due to sub-component not being loaded/defined. I don't understand this, especially since I can not get the proposed solution to work. I would also assume that whatever JS engine browser is running is smart enough to resolve import
dependencies and does not run the code if its imports are not ready. Am I over simplistic with this?
A simple reproducible example that fails deterministically is indispensable. So I have managed to created a simple replica that demonstrates the problem. For the purpose of consistency I have used the same multi-file structure of the design as in the original:
- component_a.js
class CompA extends HTMLElement
{
constructor()
{
super();
this.attachShadow({mode: "open"});
this.shadowRoot.append(CompA.template.content.cloneNode(true));
this._value = 0;
this.shadowRoot.getElementById('top').innerHTML = "A=" + this._value;
}
set value(x)
{
this._value = 2*x;
this.shadowRoot.getElementById('top').innerHTML = "A=" + this._value;
console.log('Value set on CompA');
}
}
CompA.template = document.createElement("template");
CompA.template.innerHTML = `<div id='top'></div>`;
customElements.define("comp-a", CompA);
export { CompA };
- component_b.js
import { CompA } from "./component_a.js"
class CompB extends HTMLElement
{
constructor()
{
super();
this.attachShadow({mode: "open"});
this.shadowRoot.append(CompB.template.content.cloneNode(true));
let s = this.shadowRoot.getElementById('subcomponent');
console.log(s.constructor.name);
console.log(s.matches(':defined'));
s.value = 1;
}
set value(x)
{
this.shadowRoot.getElementById('subcomponent').value = x;
console.log('Value set on CompB');
}
}
CompB.template = document.createElement("template");
CompB.template.innerHTML = `<div>
<span>Component B:</span>
<comp-a id='subcomponent'></comp-a>
</div>`;
customElements.define("comp-b", CompB);
export { CompB };
- question.js
import { CompB } from "./component_b.js"
window.onload = (event) =>
{
let x = document.createElement('comp-b');
document.body.append(x);
x.value = 10;
}
- question.html
<!doctype html>
<html lang=en>
<head>
<meta charset=utf-8>
<title>question</title>
<script type='module' src='./question.js'></script>
</head>
<body>
</body>
</html>
Expected behavior:
On question.html load <comp-b>
is created and inserted onto the page. Its setter method is called with argument of 10. During this creation of <comp-b>
in its constructor <comp-a>
is appended via the template provided. Once instantiated, it is referenced by variable s
and its setter method should be called - producing A=20
innerHTML. So the page should be:
Component B:
A=20
with the expected console output:
CompB
true
Value set on CompA
Value set on CompA
Value set on CompB
Observed behavior:
Variable s
is indeed pointing to the correct element, however s.value = 1;
does not call the setter of CompA
but simply assigns property with value of 1 to the element, thus living its default value in place. The page is:
Component B:
A=0
with the console output:
HTMLElement
false
Value set on CompB
Question:
Can someone explain me why this fails and how to force JS to associate entire specified CompA
with the s
, not only the Element
?
Please feel free to suggest further possible diagnosing of the problem?