There is a point in time when a parent custom element can access its children before they are blessed with their custom methods.
class CustomParent extends HTMLElement {
connectedCallback() {
// works
this.children[0].textContent = "bar";
// works
setTimeout(() => this.children[0].test(), 0);
// throws a Type error
this.children[0].test();
}
}
customElements.define("custom-parent", CustomParent);
class CustomChild extends HTMLElement {
test() {
this.textContent = "baz";
}
}
customElements.define("custom-child", CustomChild);
document.body.innerHTML = `
<custom-parent>
<custom-child>foo</custom-child>
</custom-parent>
`;
How can this be possible and is it safe to defer this.children[0].test()
?
It's due to the upgrading process of Custom Element.
1st step: when you execute document.body.innerHTML = '<custom-parent><custom-child>foo</custom-child></custom-parent>'
the 2 elements are inserted in the page as unknown elements.
2nd step: the parent element is upgraded first. It can access its child (and then update its textContent
property) as an unknown element. But it cannot access access the custom element test()
method... because it is not a custom element yet!
3rd step: the child element is upgraded immediately after, and now gets a test()
method.
4th step: the deferred test()
call logically works :-)
See the example below. It uses querySelectorAll( ':not(:defined)' )
to show that the child is upgraded after its parent.
class CustomParent extends HTMLElement {
constructor() { super() ; console.log( 'parent upgraded') }
connectedCallback() {
console.log( 'parent connected', this.children[0].outerHTML )
// works
this.children[0].textContent = 'bar'
// works
setTimeout( () => this.children[0].test() )
// throws a Type error
try {
this.children[0].test()
} catch ( e ) {
//this shows the parent is upgraded, but not its child
var not_upgraded = document.querySelectorAll( ':not(:defined)' )
console.log( 'not upgraded: ', ...not_upgraded )
}
}
}
customElements.define( 'custom-parent', CustomParent )
class CustomChild extends HTMLElement {
constructor() { super() ; console.log( 'child upgraded') }
test() { this.textContent = 'baz' }
}
customElements.define( 'custom-child', CustomChild )
document.body.innerHTML = `
<custom-parent>
<custom-child>foo</custom-child>
</custom-parent>`