Free Online Toolbox for developers

Introduction to Web Components

Web components encompass several technologies that allow for the creation of custom and reusable HTML tags.

In this article, I will present the entry points for creating custom HTML tags.

We will create an HTML tag called “paragraph-component” which represents a paragraph with a title, all surrounded by a border (this has no practical use and is only for the purposes of this tutorial ^^):

<paragraph-component data-title="Intro to Web Components">Content of my paragraph!</paragraph-component>

The rendering of our HTML tag:

Content of my paragraph!

What are Web components?

Web components allow creating reusable graphical user interface components without frameworks. They rely on several technologies:

  • Custom Element: A set of APIs that allows creating new HTML tags or extending standard HTML tags.
  • Shadow Dom: The Shadow DOM API is a way to encapsulate an element’s DOM. When creating a Custom Element using the Shadow DOM, its DOM is “hidden” and does not conflict with the page’s DOM. The element’s DOM is not part of the page, so a call to document.querySelector will not return it.
  • HTML template: This allows for the creation of skeletons/templates that are not displayed on the page. They can be used as a basis for creating elements.

We can use our custom tags in our projects as if they were standard HTML tags (there’s only one JS file to include).

Creating a custom element

It’s really simple to create a Custom Element, just two lines of code are enough:

class ParagraphComponent extends HTMLElement {}
customElements.define('paragraph-component', ParagraphComponent);

Small explanations:

  • customElements.define: This method allows creating a Custom Element and takes 3 parameters: the name of the Custom Element, the constructor function to create this Custom Element, and finally the options.
  • extends HTMLElement:Extending the HTMLElement class allows you to inherit the entire DOM API. In other words, any property or method you add to your class becomes part of the DOM interface of your element. It’s magical 🙂
  • paragraph-component: This is the name of our HTML tag. The name must contain a hyphen to allow the HTML parser to differentiate between standard elements and Custom Elements.

And voilĂ , now we can use our Custom Element:

<paragraph-component></paragraph-component>

We can also instantiate our custom element via JavaScript:

<script>
const myParagraph = document.createElement('paragraph-component');

// add a paragraph to my page
document.body.appendChild(myParagraph);

Events

Custom Elements have callback functions executed at different stages of their lifecycle:

CallbackExecution step
constructorDuring the element creation.
connectedCallbackThis is executed each time the element is inserted into the DOM.
disconnectedCallbackExecuted every time the element is removed from the DOM.
attributeChangedCallbackExecuted when an attribute has been added, modified, or removed. This callback function is only executed for attributes listed in the observedAttributes property.
adoptedCallbackExecuted when the custom element is moved to a new document (document.adoptNode).

Attributes

The method getAttribute allows you to retrieve the value of an attribute.

    // Getter to retrieve the attribute data-title
    get title() {
        return this.getAttribute('data-title');
    }

It is possible to be notified when the value of an attribute changes. We need to list the attributes for which we want to receive a notification in the observedAttributes property.

  static get observedAttributes() {
        // Notification when the data-title attribute changes its value
        return ['data-title'];
    }

The method attributeChangedCallback is executed every time an observed attribute’s value changes.

    attributeChangedCallback(attrName, oldVal, newVal) {
        if (attrName == 'data-title') {
       ...
       }
    }

Shadow dom

The Shadow DOM allows encapsulating the CSS and DOM of our custom element, so they won’t conflict with those of the page.

The internal DOM of our tag will not be accessible with document.querySelector.

The attachShadow method attaches a Shadow DOM tree to our custom element and returns a reference to the root node.

    constructor() {
        super();
        this.root = this.attachShadow({mode: 'closed'
});
    }

The root node is a regular element, and we can add elements to it using the appendChild method.

The <template> tag

The

<template id="myTemplate">
  <style>
    <!-- CSS of our template -->
    ...
  </style>

  <!-- HTML of our template-->
  ...
</template>

The <template> can be instantiated in JavaScript:

// Get the template
const myTemplate = document.getElementById('myTemplate');
// clone the template
const content = myTemplate.content.cloneNode(true);

...

// Then we add our template to the shadow root.
this.root.appendChild(content);

The <slot> tag

The <slot> tag represents a placeholder in a web component, and can be filled with any HTML structure. We will use this tag to display the content of the paragraph.

So we will use this tag in our template:

<template id="myTemplate">
  <style>

  <!-- :host is a CSS selector. It allows selecting a Custom Element from inside the shadow DOM.-->
  :host div {
      border: 1px solid black;
  }
  :host span {
        text-align: center;
        font-size: 3em;
  }
  </style>
  <div>
    <span id="title"></span>

    <!-- Slot will contain the HTML of the Custom Element -->
    <p><slot></slot></p>
  </div>

Here is the complete code for our Custom Element:

Below is the complete code for our paragraph-component Custom Element:

// create our template with JavaScript
let tmpl = document.createElement('template');
// we add the title in the template
tmpl.innerHTML = `
  <style>
  :host div {
      border: 1px solid black;
  }
  :host span {
        text-align: center;
        font-size: 3em;
  }
  </style>
  <div>
    <span id="title"></span>
    <p><slot></slot></p>
  </div>
`;

class ParagraphComponent extends HTMLElement {
    static get observedAttributes() {
        // We indicate to receive a notification when the data-title attribute changes its value.
        return ['data-title'];
    }
    constructor() {
        super();
        // In the constructor, we simply indicate to use shadow DOM.
        // we haven't handled the rendering yet.
        this.root = this.attachShadow({
            mode: 'open'
        });
    }

    // Getter to retrieve the data-title attribute.
    get title() {
        return this.getAttribute('data-title');
    }

    // We generate the rendering as soon as our tag is added to the DOM.
    connectedCallback() {
        this.render();
    }

    // We update the rendering when the title changes.
    attributeChangedCallback(attrName, oldVal, newVal) {
        this.render();
    }

    // Method that generates the rendering
    render() {
        // Clone  template
        const content = tmpl.content.cloneNode(true);
        // Add the title
        const title = content.getElementById("title");
        title.textContent = this.title;
        // On re-initialize the content of our tag (multiple calls to render)
        while (this.root.firstChild) this.root.removeChild(this.root.firstChild);

        this.root.appendChild(content);
    }
}

window.customElements.define('paragraph-component', ParagraphComponent);


Extending an existing element

Web Components also allow us to extend standard HTML tags, as well as custom elements!

We can easily extend our paragraph:

class SuperParagraphComponent extends ParagraphComponent {
}
window.customElements.define('super-paragraph-component', SuperParagraphComponent );

Conclusion

Great, I hope this tutorial was helpful. It covered the basics of creating custom HTML tags using Web Components. It’s worth noting that we only scratched the surface, and there is much more to learn and explore!

Also, keep in mind that you don’t have to create Custom Elements using pure JavaScript. You can create them with frameworks like Angular, Polymer Project, and more.

You can find the code for this tutorial on CodePen (In French).




Leave a Reply