Search code examples
javascripthtmlweb-component

Cannot use Web Components more than once


I'm new to vanilla javascript as i have experience only in reactjs. I'm trying to display a web component i have created in two places. But it displays only once. What can be the issue here?

My web component

class MyComponent extends HTMLElement {
    constructor() {
        // always call super() first
        super();
        console.log('constructed!');
    }

    connectedCallback() {
        // console.log(this.getAttribute("filter"));
        this.innerHTML = `
          <html>
            <div id="templates" class="template_style">
              <template id="book-template">
                <li><span class="title"></span> &mdash; <span class="author"></span></li>
              </template>

             <ul id="books"></ul>
           </div>

         </html>
       `;
    }
}

customElements.define('my-component', MyComponent);

JS code used to populate the web component with data

import JsonObj from './debts.js';

const books = JsonObj;

function appendBooks() {
    const booksList = document.getElementById('books');
    const fragment = document.getElementById('book-template');

    // Clear out the content from the ul
    booksList.innerHTML = '';
    console.log(books);
    // Loop over the books and modify the given template
    books.debts.forEach(book => {
        // Create an instance of the template content
        const instance = document.importNode(fragment.content, true);
        // Add relevant content to the template
        instance.querySelector('.title').innerHTML = book.bank;
        instance.querySelector('.author').innerHTML = book.category;
        // Append the instance ot the DOM
        booksList.appendChild(instance);
    });
}


document.getElementById('templates').addEventListener('change', (event) => appendBooks());

appendBooks('book-template');

HTML code where i'm using the above component

<div>

    <div class="sub_container">

        <label>Users in Debt</label>

        <input placeholder="Search by user name" class="input_style">

        <my-component filter="userdebt"></my-component>


    </div>

    <div class="sub_container">

        <label>Debts</label>

        <input placeholder="Search by debt description" class="input_style">

        <my-component filter="debt"></my-component>

    </div>

</div>

So as seen in the html code i have used </my-component> in two places but i can only see the first instance only. I have used a console.log inside constructor in my component and i can see it calls 2 times. But in the view i can see only the first instance. How can i fix this?


Solution

  • You should add id to the web-component itself so that you can query using the id of the parent for the child nodes that is contained within it

    In your code the when queried using the id of the child of the web-component it fetched you the first one since you had duplicated ids, in my code I gave each <my-component> tag a unique id so that I can only query for its child:

    class MyComponent extends HTMLElement {
        constructor() {
            // always call super() first
            super();
            console.log('constructed!');
        }
    
        connectedCallback() {
            // console.log(this.getAttribute("filter"));
            this.innerHTML = `
                <div id="templates" class="template_style">
                  <template id="book-template">
                    <li><span class="title"></span> &mdash; <span class="author"></span></li>
                  </template>
    
                 <ul id="books"></ul>
               </div>
           `;
        }
    }
    
    customElements.define('my-component', MyComponent);
    const books = [{bank : "foo", category: "bar"}];
    
    function appendBooks(id) {
        const comp = document.getElementById(id);
        //query for the child of the parent web-component
        const booksList = comp.querySelector('#books'); 
        const fragment = comp.querySelector('#book-template');
       
    
        // Clear out the content from the ul
        booksList.innerHTML = '';
       
        // Loop over the books and modify the given template
        books.forEach(book => {
            // Create an instance of the template content
            const instance = document.importNode(fragment.content, true);
            // Add relevant content to the template
           
            instance.querySelector('.title').innerHTML = book.bank;
            instance.querySelector('.author').innerHTML = book.category;
            // Append the instance ot the DOM
            booksList.appendChild(instance);
        });
    }
    
    function addChangeListener(id){
      document.getElementById(id)
              .querySelector('#templates')
              .addEventListener('change', (event) => appendBooks());
    }
    
    appendBooks('comp1'); //for componenent having id = comp1
    appendBooks('comp2'); //for componenent having id = comp2
    addChangeListener('comp1')
    addChangeListener('comp2')
    <div>
    
        <div class="sub_container">
    
            <label>Users in Debt</label>
    
            <input placeholder="Search by user name" class="input_style">
    
            <my-component id="comp1" filter="userdebt"></my-component>
    
    
        </div>
    
        <div class="sub_container">
    
            <label>Debts</label>
    
            <input placeholder="Search by debt description" class="input_style">
    
            <my-component id="comp2" filter="debt"></my-component>
    
        </div>
    
    </div>