Search code examples
javascriptweb-componentshadow-domcustom-element

Composing v1 nested web components


I'm new to webcomponents. Since v1 of webcomponents is available, I'm starting there. I have read various posts around the web about them. I'm particularly interested in composing them correctly. I have read about slots and have them working, although my efforts have not resulted in slotted webcomponents that work the way I expected.

If I have compose nested web components like this, the DOM from the nested/slotted webcomponent does not get inserted in the slot of the parent:

<parent-component>
  <child-component slot="child"></child-component>
</parent-component>

And here's the parent webcomponent HTML:

<div id="civ">
  <style>
  </style>
  <slot id="slot1" name="child"></slot>
</div>

Since each webcomponent (parent and child) is written to be independent, I have been creating them with:

customElements.define('component-name', class extends HTMLElement {
  constructor() {
    super();
    this.shadowRoot = this.attachShadow({mode: 'open'});
    this.shadowRoot.innerHTML = `HTML markup`
  }
})

The resulting DOM includes 2 shadow roots. I have attempted to write child/slotted elements without shadow dom, which also does not result in the parent shadow dom hosting the children.

So, what is the right way to compose v1 nested webcomponents?


Solution

  • First, you must use a browser that implements Shadow DOM and Custom Elements v1.

    Then the call to attachShadow() will assign the new Shadow DOM automatically to the read-only property shadowRoot.

    You can append the HTML code to the Shadow DOM's innerHTML but I'd recommend to use a <template>'s content property instead.

    Then nesting is natural:

    customElements.define( 'parent-component', class extends HTMLElement {
        constructor() {
            super()
            this.attachShadow( {mode: 'open'} )
            this.shadowRoot.appendChild( parent1.content.cloneNode( true ) )
        }
    } )
                
    customElements.define( 'child-component', class extends HTMLElement {
        constructor() {
            super()
            var sh = this.attachShadow( {mode: 'open'} )
            sh.appendChild( child1.content.cloneNode( true ) )
        }
    } )
    <parent-component>
        <child-component slot="child">
            <span>Hello</span>
        </child-component>
    </parent-component>
    
    
    <template id="parent1">
        <style>
            :host { background: lightblue ; display: inline-block }
            ::slotted( [slot=child] ) { background: lightyellow }
        </style>
        <h4>(parent)</h4>
        <p>Slotted child:
            <slot name="child"></slot>
        </p>
    </template>    
    
    <template id="child1">
        <style>
            :host { display: inline-block }
            ::slotted( span ) { background: pink }
        </style>
        <h5>(child)</h5>
        <span>Nested slot: <slot></slot></span>
    </template>

    In the <style> tags, use:

    • :host to style the custom element itself, and
    • ::slotted() to style the elements inserted with a <slot> tag.