Search code examples
javascripthtmltemplatesweb-componentseparation-of-concerns

How to separate web components to individual files and load them?


I have a web component x-counter, which is in a single file.

const template = document.createElement('template');
template.innerHTML = `
  <style>
    button, p {
      display: inline-block;
    }
  </style>
  <button aria-label="decrement">-</button>
    <p>0</p>
  <button aria-label="increment">+</button>
`;

class XCounter extends HTMLElement {
  set value(value) {
    this._value = value;
    this.valueElement.innerText = this._value;
  }

  get value() {
    return this._value;
  }

  constructor() {
    super();
    this._value = 0;

    this.root = this.attachShadow({ mode: 'open' });
    this.root.appendChild(template.content.cloneNode(true));

    this.valueElement = this.root.querySelector('p');
    this.incrementButton = this.root.querySelectorAll('button')[1];
    this.decrementButton = this.root.querySelectorAll('button')[0];

    this.incrementButton
      .addEventListener('click', (e) => this.value++);

    this.decrementButton
      .addEventListener('click', (e) => this.value--);
  }
}

customElements.define('x-counter', XCounter);

Here the template is defined as using JavaScript and html contents are added as inline string. Is there a way to separate template to an x-counter.html file, css to say, x-counter.css and corresponding JavaScript code to xcounter.js and load them in index.html?

Every example I lookup has web components mixed. I would like to have separation of concerns, but I am not sure how to do that with components. Could you provide a sample code? Thanks.


Solution

  • In the main file, use <script> to load the Javascript file x-counter.js.

    In the Javascript file, use fetch() to load the HTML code x-counter.html.

    In the HTML file, use <link rel="stylesheet"> to load the CSS file x-counter.css.

    CSS file : x-counter.css

    button, p {
        display: inline-block;
        color: dodgerblue;
    }
    

    HTML file : x-counter.html

    <link rel="stylesheet" href="x-counter.css">
    <button aria-label="decrement">-</button>
        <p>0</p>
    <button aria-label="increment">+</button>
    

    Javascript file : x-counter.js

    fetch("x-counter.html")
        .then(stream => stream.text())
        .then(text => define(text));
    
    function define(html) {
        class XCounter extends HTMLElement {
            set value(value) {
                this._value = value;
                this.valueElement.innerText = this._value;
            }
    
            get value() {
                return this._value;
            }
    
            constructor() {
                super();
                this._value = 0;
    
                var shadow = this.attachShadow({mode: 'open'});
                shadow.innerHTML = html;
    
                this.valueElement = shadow.querySelector('p');
                var incrementButton = shadow.querySelectorAll('button')[1];
                var decrementButton = shadow.querySelectorAll('button')[0];
    
                incrementButton.onclick = () => this.value++;
                decrementButton.onclick = () => this.value--;
            }
        }
    
        customElements.define('x-counter', XCounter);
    }
    

    Main file : index.html

    <html>
    <head>
        <!-- ... -->
        <script src="x-counter.js"></script>
    </head>
    <body>
        <x-counter></x-counter>
    </body>
    </html>