So I have read several pieces that say if you want a custom event to traverse the shadow DOM boundary and cross into the light DOM you need to set the custom event's composed
property to true. I noticed however that any events I dispatch from a web component's this.
make it out of the shadowRoot component just fine, and ones that are dispatched from this.shadowRoot
stay inside. So why do I need the "composed" property? Am I doing something wrong?
const internalEvent = new CustomEvent("internalEvent", {bubbles: true, cancelable: false})
const externalEvent = new CustomEvent("externalEvent", {bubbles: true, cancelable: false})
class MyComponent extends HTMLElement {
constructor() {
super()
this.attachShadow({ mode: 'open' })
this.shadowRoot.innerHTML = `
<button id="internalButton">INTERNAL</button>
<button id="externalButton">EXTERNAL</button>
`
this.internalButton = this.shadowRoot.getElementById("internalButton")
this.externalButton = this.shadowRoot.getElementById("externalButton")
}
connectedCallback() {
this.internalButton.addEventListener("click", ()=>{
this.shadowRoot.dispatchEvent(internalEvent)
})
this.externalButton.addEventListener("click", ()=>{
this.dispatchEvent(externalEvent)
})
this.shadowRoot.addEventListener("internalEvent", (event)=>{
console.log("Internal event detected internally.")
})
this.shadowRoot.addEventListener("externalEvent", (event)=>{
console.log("External event detected internally!")
})
}
}
document.addEventListener("internalEvent", ()=>console.log("Internal event detected externally!"))
document.addEventListener("externalEvent", ()=>console.log("External event detected externally."))
customElements.define('my-component', MyComponent)
edit: I'm just struggling to think of any reason where, to get a message to leave your component, you'd prefer to dispatch it within the shadowRoot and add a special property, rather than just dispatching it straight into the light DOM in the first place.
'this' is the Custom Element/Web Component <my-component>
,
'this' is NOT inside the elements shadowRoot.
So Events you dispatch from 'this', do not cross shadowDOM boundaries.
You only need composed: true
when Events need to cross (aka "escape") shadowDOM –
<script>
const EventName = "HelloFromComponent";
customElements.define('my-component', class extends HTMLElement {
constructor() {
let attach = (btn, composed = false, el = this.shadowRoot.getElementById(btn)) =>
el.onclick = () => {
el.dispatchEvent(new CustomEvent(EventName, {
bubbles: true,
cancelable: false,
composed: composed
}))
}
super().attachShadow({mode: 'open'})
.innerHTML = `<button id="one">One</button><button id="two">Two</button>`;
attach("one", /* composed = */ false );
attach("two", /* composed = */ true );
}
listen(where) {
where.addEventListener(EventName, (evt) => {
console.log(where.nodeName, evt.type, evt.composed, );
})
}
connectedCallback() {
this.listen(this);
this.listen(document);
}
disconnectedCallback(){
// remove any listeners attached *outside* this element!!!
}
});
</script>
<my-component></my-component>