Nesting Custom Web Components

While continuing my study and branching out into web components – I am pretty new to them and my main knowledge of them comes somewhat from React – I am remaking this contact app: https://makingstuffs.co.uk (yellow button) into a web component based app.

My thinking is that I will be able to make it all more modular by breaking down the entire app into the following parts:

Main modal – The overall wrapper which will be called
Screens – One for messaging, one for portfolio
Info Bar – Stays the same on both screens but houses nav buttons
Buttons – Nav buttons and a send button
Keypads - One for special characters one for letters

In React I would define the MainModal component which would extend React.Component, then I would define the Screen component which would extend MainModal and everything else would extend the Screen component – I could be wrong here, I am not a React genius in any way, shape, or form.

However, I cannot seem to find any similar functionality with web components. I am guessing I could, possibly, define EVERYTHING in the same JS file in a falling tree structure but that would lead to one massive file – bigger than the original vanilla JS file – and would be even more confusing to read.

I then found <template></template> and <slot></slot>. I understand how they work – define them in HTML then call and clone them in the web component – but they just seem like they would be so cumbersome for my specific project and would lead to a lot of setup for the end user.

Is there something I am missing?

In your example, Screens would be children of Main Modal, rather than its instances, meaning that you will nest them inside MainModal, but not initialize from it. In custom elements you would do exactly the same with <slot>:

<main-modal>
  <main-screen>...something else</main-screen>
</main-modal>
1 Like

Howdy again :laughing:

Yeah, I think I should phrase my post better… I tried making multiple components which expand the main modal, but, for whatever reason just could not get it properly attach to the shadow DOM.

Would you recommend just make screen component:

class Screen extends HTMLElement {
    constructor(){
        super()
        // stuff that happens
    }
}
customElement.define('modal-screen', Screen);

And then import it into the main modal (as it is the entry point) then create/append an instance of it to the shadow DOM from within the main modal, like so:

import screen from './screen-component-file.js';
class MainModal extends HTMLElement {
    constructor(){
        super()
        // stuff that happens
        const shadow = this.attachShadow({mode: 'open'}),
        screen = document.createElement('modal-screen');
        shadow.appendChild(screen);
    }
}
customElement.define('main-modal', MainModal);

And just call the following in the HTML index:

<main-modal></main-modal>

Or would you suggest just doing importing the component into the entry point and calling the nested component in HTML, like your example:

<main-modal>
    <modal-screen>
    </modal-screen>
</main-modal>

Sorry if this seems a bit long and dumb!

Forgot to update this post… the solution is, as said by @snigo using nested elements, and using templates. So now my code is as follows:

Define the outer wrapper (phone):

customElements.define( 'iphone-modal', class extends HTMLElement {
    constructor(){
        super();
        const shadow = this.attachShadow({ mode: 'open' }),
            stylesheet = msl.create('style');
        stylesheet.innerHTML = styles.toString();
        shadow.appendChild(stylesheet);
        shadow.appendChild(phoneTemplate.content.cloneNode( true ));
    }
});

Define the inner element (screen):

customElements.define( 'iphone-screen', class extends HTMLElement {
    constructor(){
        super()
        const shadow = this.attachShadow({mode: "open"});
        shadow.appendChild(phoneScreen.content.cloneNode( true ));
    }
}

Now, in the HTML file I need to define the <template></template> for the screen and the phone, this will use the <slot></slot> system to insert the <iphone-screen></iphone-screen> element into the <iphone-modal></iphone-modal> element:

        <!-- outer modal template -->
        <template id="phoneTemplate">
            <div class="wrapper">
                <!-- slot the screen inside the wrapper -->
                <slot name="screen"> </slot>
            </div>
        </template>
        <!-- iphone screen template which will be slotted into the wrapper above -->
        <template id="phoneScreen">
            <!-- Whatever you want to be in this element, could be another component if you wish -->
            <h1>Hey</h1>
        </template>

Finally, to piece it all together I call the custom elements while defining the <iphone-scree></iphone-screen> element as the one which will occupy the slot defined above:

        <iphone-modal>
            <iphone-screen slot="screen"></iphone-screen>
        </iphone-modal>
1 Like