Search code examples
javascripttemplatesweb-component

Single HTTP request for HTML template file for multiple instances of the same Web Component


Say I have a Web Component defined like this:

// web-component.js
export class WebComponent extends HTMLElement {
  template = '/path/to/template.html';  
  tmpl = {};

  constructor() {
    super();
  }
    
  async connectedCallback() {
    const html = fetch(this.template).then(response => response.text());
    this.doSomething(await html);
  }
  
  doSomething(html) {
    console.log(html);
  }
}

document.addEventListener('DOMContentLoaded', customElements.define('web-component', WebComponent));

A template file like this:

//template.html
<template id="web-component">
    <h1>Text Goes Here</h1>
</template>

And a web page like this:

//index.html
    ....
    <head>
    <script type="module" src="/path/to/web-component.js"></script>
    </head>
    <body>
    <web-component>Foo</web-component>
    <web-component>Bar</web-component>
    <web-component>Baz</web-component>
    </body>
    ....

The web browser is making three http requests to fetch the same template file. I want to store the html from the template file somewhere on the client so I'm only making a single http request.

I tried this:

async connectedCallback() {
  const existing_template = document.body.querySelector('#web-component');
  console.log(existing_template);
  if (existing_template) {
    this.doSomething(existing_template);
  } else {
    const html = fetch(this.template).then(response => response.text());
    this.addTemplateToWebPage(await html);
    this.doSomething(await html);
}

addTemplateToWebPage(html) {
    const tag = document.createElement('body');
    tag.innerHTML = html;
    document.body.appendChild(tag.querySelector('template'));
}

But existing_template is always null, so I'm still making unnecessary http requests. I also tried this, with the same result:

connectedCallback() {
    this.doSomething();
  }

  async doSomething() {
    const existing_template = document.body.querySelector('#web-component');
    console.log(existing_template);
    if (existing_template) {
      this.doSomethingElse(existing_template);
    } else {
      const html = fetch(this.template).then(response => response.text());
      this.addTemplateToWebPage(await html);
      this.doSomethingElse(await html);
    }
  }

  doSomethingElse(html) {
    console.log('doing something else');
  }

How can I do this so I only have a single http request when calling the same template?


Solution

    • Add a static class property, initialized as FALSE
    • In the class constructor, check the value of the static property and, if needed, fetch the template file.
    • In the constructor, set the value of the static property with the result of the fetch.
    • Flag the connectedCallback method as async and use AWAIT to retrieve the static class property.
    • Clone the template to use in the instance(s).

    my-component.js

    class MyComponent extends HTMLElement {
      static file = '/path/to/template.html';
      static template = false;
    
      constructor() {
        super();
        if (!MyComponent.template) {
          MyComponent.template = MyComponent.fetchTemplate();
        }
      }
    
      async connectedCallback() {
        const tmpl = await MyComponent.template;
        this.tmpl = tmpl.cloneNode(true);
        console.log(this.tmpl);
      }
    
      static async fetchTemplate() {
        return await fetch (MyComponent.file)
        .then (response => response.text())
        .then (txt => {
          const frag = document.createRange().createContextualFragment(txt);
          return frag.querySelector('template').content;
        });
      }
    }
    

    template.html

    <template id="my-component">
        <h1>Text Goes Here</h1>
    </template>