Search code examples
javascriptcomponentsvue-componentweb-componentangular-components

Web Components in Vanilla JS: Equivalent of ngFor and v-for?


I have a custom component defined in vanilla JS:

class Article extends HTMLElement {
  constructor() {
    super();
  }

  connectedCallback() {
    this.render();
  }

  render() {
    this.innerHTML = /*html*/ `
    <div>this.getAttribute("title")</div>
    `;
  }
}

customElements.define("custom-article", Article);

I then would like to instantiate these components from an array, specifically (in the HTML):

This is trivial to do in both Angular and Vue.js (and probably in every other JS framework), however I can't find documentation on the same functionality being implemented in Vanilla JS.


Solution

  • Sadly, JS template literals don't come with such functionality yet - you're going to find yourself working less and less with HTML strings when working on vanilla JS webcomponents.

    Here's an example of a string-based approach in vanilla JS, rendering a list of fruits:

    class FruitList extends HTMLElement {
      ul = document.createElement('ul');
      fruits = ['Apple','Banana','Strawberry','Ananas','Cherry'];
      
      constructor() {
        super().attachShadow({mode: 'open'}).append(this.ul);
      }
    
      connectedCallback() {
        this.render();
      }
    
      render() {
        this.ul.innerHTML = this.fruits.reduce((s,f)=>s+=`<li>${f}</li>`,'');
      }
    }
    
    customElements.define("fruit-list", FruitList);
    <fruit-list></fruit-list>

    Yet for simple HTML that is to be created, dropping the use of strings altogether works just fine:

    class FruitList extends HTMLElement {
      ul = document.createElement('ul');
      fruits = ['Apple','Banana','Strawberry','Ananas','Cherry'];
      
      constructor() {
        super().attachShadow({mode: 'open'}).append(this.ul);
      }
    
      connectedCallback() {
        this.render();
      }
    
      render() {
        while (this.ul.firstChild) this.ul.firstChild.remove();
        this.ul.append(...this.fruits.map(fruit => {
          const li = document.createElement('li');
          li.textContent = fruit;
          return li;
        }));
      }
    }
    
    customElements.define("fruit-list", FruitList);
    <fruit-list></fruit-list>

    This also has the neat advantage that you can much more easily e.g. attach event listeners to the dynamically created elements.