DEV Community

Raghav Misra
Raghav Misra

Posted on • Updated on

Better Web Components Pt. 1: Rendering Children ✔️

Hey! This is my first DEV article ever, and I would love feedback on how to improve! This is going to be a series, where in each article, I go over one cool trick to help you out (hopefully).

Web Components are really cool in my opinion! Think of the possibility! A powerful UI component framework, without using a framework!

Sometimes however, they can get inefficient to write. This, for the most part, is due to web components being very unopinionated. There is no state-management, no data-binding, etc. These features are often found in libraries and frameworks such as React or lit-element, because the libraries are abstractions over native JS APIs. On the other hand, Web Components are just APIs that are a part of the spec.

Problem Statement:

Custom Elements are nothing without what they contain (child nodes). Yet in Vanilla JavaScript, creating child nodes are a pain.

Let's say you want to render the following markup to a custom element (where {name} and {date} represent placeholders that need to be changed):

<h1>Hello, {name}</h1>
<p>Today is {date}</p>
Enter fullscreen mode Exit fullscreen mode

How would we render this to a vanilla custom element?

Built-in Solutions:

Solution 1: Vanilla DOM API Calls ❌

We can use the native DOM APIs to accomplish this.

class DateName extends HTMLElement {
    connectedCallback() {
        const h1 = document.createElement("h1");
        const p = document.createElement("p");

        h1.innerText = `Hello, ${this.dataset.name}`;
        p.innerText = `Today is ${this.dataset.date}`;

        this.appendChild(h1);
        this.appendChild(p);
    }
}

customElements.define("date-name", DateName);
Enter fullscreen mode Exit fullscreen mode

This works, but for more complex components, the amount of code needed is very high compared to the HTML code we end up with. Secondly, our code is hard to visualize, and is therefore annoying to debug.

Solution 2: innerHTML

The innerHTML property allows us to directly modify the HTML code inside the custom element, exactly how we would in an HTML document.

class DateName extends HTMLElement {
    connectedCallback() {
        this.innerHTML = (`
            <h1>Hello, ${this.dataset.name}</h1>
            <p>Today is ${this.dataset.date}</p>
        `);
    }
}

customElements.define("date-name", DateName);
Enter fullscreen mode Exit fullscreen mode

This solves all of our problems! Right? NO! While the innerHTML approach is definitely easier to read and write, it makes the browser work much harder, which decreases performance by quite a bit. Additionally, this method is very prone to XSS Attacks (more on that here).

Solution 3: HTML Templates ✔️

HTML Template Element (part of the Web Components spec) is a DOM element, where you can write normal HTML code (markup, inline scripts and styles), but nothing will be applied to the DOM. Better explanation here.

In your HTML:

<template>
    <h1>Hello, <slot id="name"></slot></h1>
    <p>Today is <slot id="date"></slot></p>
</template>
Enter fullscreen mode Exit fullscreen mode

Your Client-Side JS:

// Template:
const dateNameTemplate = document.querySelector("template");

// Custom Element
class DateName extends HTMLElement {
    connectedCallback() {
        // Copy the nodes in the template:
        const clone = dateNameTemplate.content.cloneNode(true); 

        // Replace Placeholders:
        const name = clone.querySelector("#name").parentNo = this.dataset.name;
        clone.querySelector("#date").innerText = this.dataset.date;

        // Append Node:
        this.appendChild(clone);
    }
}

customElements.define("date-name", DateName);
Enter fullscreen mode Exit fullscreen mode

Honestly, using HTML Templates is definitly very concise, and I personally really like this method. However, with more advanced components, the conciseness can go away, especially when there is a lot of different placeholders to replace. Regardless, this solution is effective, efficient, and secure.

Best Built-in Solution:

Out of the built-in solutions, the use of HTML templates is the best. The reasoning behind this is that it provides the perfect balance between performance (at par with the native DOM API method) and conciseness (easy to read, especially with well-commented code). However, all of the above solutions are viable, except for maybe using innerHTML due to security issues.

For any of the above solutions, the following HTML should work:

<date-name data-name="raghavm" data-date="Jan. 19, 2020"></date-name>
Enter fullscreen mode Exit fullscreen mode

Hope this helped! Stay tuned for the next one :)

Top comments (3)

Collapse
 
gypsydave5 profile image
David Wickes

Couldn't help but notice that the HTML you used with the template is different to the other solutions - the additional span elements. Is there any way around this?

Collapse
 
raghavmisra profile image
Raghav Misra

I realized this problem, but chose to ignore it for the sake of simplicity. I will edit the post to fix this problem. Thanks!

To fix this, I would replace the entire placeholder <span> with the text, rather than changing its innerText. I'm sure there are other solutions, so let me know if you find one. ;D

Collapse
 
raghavmisra profile image
Raghav Misra

Another, much easier, solution would be to replace the <span> elements with <slot> elements. Although slots were made for use with the Shadow DOM, they aren't limited to that. The purpose of a slot is to be replaced, which means it's unlikely that they will be styled.