Search code examples
javascripthtmldomshadow-dom

Is it possible to add elements within a custom element without using `<template>` and template slots?


I'm trying to create a custom component which can take other HTML Elements, however, it has to be flexible by not having to use <template> and slots templates in the HTML file every time the component is used. Already tried querying after every render by using const children = this.shadow.querySelectorAll('.menu > *') within the connectedCallback() function, but the result is an empty NodeList

class SideMenu extends HTMLElement {
    constructor() {
        super();
        this.shadow = this.attachShadow({mode:'open'});
    }

    render() {
        this.shadow.innerHTML = `
        <style>
          .menu {
            display: flex;
            flex-direction: column;
            height: 100vh;
            padding: 2em;
            width: 33vw;
            gap: 10px;
            background-color: #2C2B2B;
          }
        </style>
        <div class="menu">
        </div>
        `;
    }

    connectedCallback() {
        this.render();
    }
}

customElements.define('side-menu', SideMenu);
<side-menu>
    <div>Element1</div>
    <div>Element2</div>
    <div>Element3</div>
    <div>ElementN</div>
</side-menu>

Basically, the custom element has to be able to have any number of elements within, like an unordered list (<ul>) which can have multiple <li> inside.


Solution

    • You have a shadowDOM, so don't need an extra div when you style the component with :host

    • you can manually move ligthDOM elements to shadowDOM

    • super() sets AND returns the 'this' scope

    • attachShadow sets AND returns this.shadowRoot

    • append wasn't available in Internet Explorer, ("oldfashioned" devs still use appendChild)
      note it moves the DOM elements from lightDOM to shadowDOM. And if you move Web Components, the connectedCallback will run again (but lightDOM will then be empty) So be aware what you do in the connectedCallback

    customElements.define('side-menu', class extends HTMLElement {
        constructor() {
            super().attachShadow({mode:'open'}).innerHTML = `
            <style>
              :host {
                display: flex; flex-direction: column; gap: 10px;
                height: 100vh; width: 33vw;
                padding: 2em;
                background-color: #2C2B2B; color: gold;
              }
            </style>`;
        }
    
        connectedCallback() {
            this.shadowRoot.append(...this.children);
        }
    });
    <side-menu>
        <div>Element1</div>
        <div>Element2</div>
        <div>Element3</div>
        <div>ElementN</div>
    </side-menu>