run_the_race
run_the_race

Reputation: 2368

How to insert an element into a web component's slot via JavaSCript

In the below examples, foo is the parent component, and bar is the child that fits into foo's slot.

As you can see below I can slot an element if I place it in the HTML directly, but when I try slot an element via JavaScript, it is not slotted, and is not styled by the ::slotted(*) selector. How does one append an element to a web component's slot?

class ComponentFoo extends HTMLElement {
    constructor() {
        super();
        this.attachShadow({ mode: "open" });
        this.shadowRoot.appendChild(TEMPLATE_FOO.content.cloneNode(true));

    }
    connectedCallback() {
        const slotEl = this.shadowRoot.getElementById("SLOT");
        const bar = document.createElement("wc-bar");
        bar.textContent = "Bar created via Javascript"
        slotEl.appendChild(bar)  // <--- Does not work
        console.log(bar)
    }
};
window.customElements.define("wc-foo", ComponentFoo);

class ComponentBar extends HTMLElement {
    constructor() {
        super();
        this.attachShadow({ mode: "open" });
        this.shadowRoot.appendChild(TEMPLATE_BAR.content.cloneNode(true));
    }
};
window.customElements.define("wc-bar", ComponentBar);
<template id="TEMPLATE_FOO">
  <div>foo</div>
  <slot id="SLOT"></slot>
  <style>
    ::slotted(*) {
        color: red;
        border: 1px solid lime;
    }
  </style>
</template>

<template id="TEMPLATE_BAR">
  <div>bar</div>
</template>

<wc-foo>
    <div>bar created via HTML</div>
</wc-foo>

Upvotes: 0

Views: 2858

Answers (1)

Because slotted content is NOT moved to a <slot>!

slotted content is REFLECTED into the slot from its container lightDOM

So if you assign content to a <slot> it becomes its default content, shown only when there is nothing reflected in the <slot> (thus :slotted won't style it, because there is nothing slotted)

For :slotted deep dive, see: ::slotted CSS selector for nested children in shadowDOM slot

const createElement = (t,p={}) => Object.assign(document.createElement(t),p);
class MyBaseClass extends HTMLElement{
  constructor() {
      super()
       .attachShadow({mode:"open" })
       .append(
         document.getElementById(this.nodeName).content.cloneNode(true),
         createElement("style", {
           innerHTML: `:host{ display:block;margin:5px }`       
         })
       );
  }
}
customElements.define("wc-foo", class extends MyBaseClass {
  connectedCallback() {
      this.append(// TO: lightDOM!!!
        createElement("wc-bar", {
          innerHTML: "Bar created via Javascript"
        })      
      ); 
      this.shadowRoot
          .querySelector("slot[name='unused']")
          .append("Hello default content in an empty slot");
  }
});

customElements.define("wc-bar", class extends MyBaseClass {});
<template id="WC-FOO">
  <div>div in WC-FOO</div>
  <slot></slot>
  <slot name="unused"></slot>
  <style>
    ::slotted(*) {
        background: pink;
    }
  </style>
</template>

<template id="WC-BAR">
  <style>* { background: lightgreen } </style>
  <div>div in WC-BAR</div>
  <slot></slot>
</template>

<wc-foo>
    <div>Line Foo 1</div>
    <wc-bar>Line Foo 2</wc-bar>
</wc-foo>

Upvotes: 4

Related Questions