LNSY.DEV

UX for AI

Email

Custom HTML Elements

Last Updated 30 Nov, 2023


Introduction

My favorite feature of ES6+ is Custom HTML Elements. Custom Elements allow us to extend HTML for React-Like components without the need for preprocessors or compilers. There are fewer things "out of the box" for these components, but that means they can be lighter and more readable than react.

You can use these components as you would any other html elements. Each component needs a dash in its name. So

<componentelement></componentelement>

is not valid, but

<component-element></component-element>

is.

A basic element

Here is a basic element that gets an attribute called
title on load and sets its internal HTML to it.


class CustomHTMLElement1 extends HTMLElement {
  constructor(){
    super();
    this.title = this.getAttribute('title');
    if(this.title === null){
      this.title = 'CUSTOM ELEMENT';
    } 
    this.innerHTML = `

${this.title}

`; } } customElements.define('custom-element-1', CustomHTMLElement1);

If we instantiate this component with:

<custom-element-1 title="This sets the title Attribute"></custom-element-1>

We get this result

Change an attribute with Javascript

We can change attributes by setting observedAttributes and attributeChangedCallback functions. If we wanted to change the "title" attribute with javascript the new component would look like:

  
    
class CustomHTMLElement2 extends HTMLElement {
  constructor(){
    super()
    this.title = this.getAttribute('title');
    if(this.title === null){
      this.title = 'CUSTOM ELEMENT';
    } 
    this.innerHTML = `

${this.title}

` } static get observedAttributes() { return ['title']; } attributeChangedCallback(name, oldVal, newVal) { if(name === 'title'){ this.innerHTML = `

${newVal}

` } } } customElements.define('custom-element-2', CustomHTMLElement2)

If we instantiate a component like this:

<custom-element-2 id="custom_element_2" title="old title"></custom-element-2>

We can then change the title like so:

setInterval(() => {
  custom_element_2.setAttribute('title', Math.random());
},1000);

And the result looks like this:

An Asynchronous Loading Pattern

I use this pattern a lot for components that might take a while to load. This component introduces the connectedCallback function. The connectedCallback function runs when the component is attached to the DOM. It runs after the constructor. In this example I use connectedCallback to run an asynchronous function called init.

class AsyncComponent extends HTMLElement {
  constructor(){
    super()
    this.title = this.getAttribute('title');
    if(this.title === null){
      this.title = 'CUSTOM ELEMENT';
    } 
    this.innerHTML = `Loading...`
  }

  connectedCallback(){
    this.init();
  }

  slowResponse(){
    return new Promise((res, rej) => {
      setTimeout(() => {
            res();
      }, 3000)
    })
  }

  async init(){
    await this.slowResponse()
    this.innerHTML = 'Component Loaded!'
  }

  static get observedAttributes() {
    return ['title'];
  }

  attributeChangedCallback(name, oldValue, newValue) {
    if(name === 'title' && newValue !== null){
      this.innerHTML = `

${newValue}

` } } } customElements.define('async-component', AsyncComponent);

This function waits for the slowResponse function to run, and then changes its content to loaded.

Click this button to create a new async-component:

Styling Custom Components

Components can be styled like any other HTML Element. Initially in the box model they act like span tags, so style your display tag in CSS accordingly.

class StyledComponent extends HTMLElement {
  constructor(){
    super()
    this.innerHTML = 'This is a Styled Component'
  }
}
customElements.define('styled-component', StyledComponent);
styled-component {
  background-color: red;
  color: white; 
  border: 1px dashed black;
  padding: 1em;
  display:block;
}