loading...
Cover image for Web Components Fundamentals, Part 1/3

Web Components Fundamentals, Part 1/3

pdesjardins90 profile image Philippe Desjardins Updated on ・7 min read

^ This is our little Rosie :)

Overview

If you're new to web development, you probably already noticed how complicated it is just to get started. If you're an old fart you've probably cried on your keyboard at least once when time came to ditch a framework you finally became proficient with. In such an ecosystem of ever-multiplying frameworks and libraries, two things are certain:

  • Whatever framework or library you choose to begin with, it'll go out of fashion at some point.

  • The only thing you will carry on with you at that point will be your knowledge of fundamentals. I'm talking plain HTML, CSS, JavaScript, the way web browsers work and computer science fundamentals.

One thing that used to go to the toilet every time such a shift occured in the web development landscape was the component model. Every framework had their own special way of building a reusable UI component, so all time invested in learning how it worked was gone with the rest of the framework.

Well, no more. Enter standard web components.

Yep, browser makers finally caught up with modernity and included a standard way of building such components, meaning that there's now a way of writing these once and keep them with you until the end of the internet! I'm not saying you won't ever write framework-specific components, but hopefully they'll become the exception.

Below, I'll explain the concepts behind native web components, how to create them and I'll show you a neat library that currently simplifies how to create them. The library will die at some point no doubt (especially since it's made by Google), but when that happens, you'll keep your understanding of the underlying concepts :)

WARNING

We'll be creating DOM elements in this page manually, which is tedious but cool when trying to understand those concepts. In the next part of this post we'll use markup and much more fun tools, stay tuned.

Custom elements

You know of <div> right? It's a standard HTML tag. When a browser reads an HTML file and sees a <div>, it creates an HTMLDivElement and stamps it in the DOM tree where the HTML file specified it. You don't have to define it since it's already part of the platform, nor do you need to instanciate it yourself, you just need to specify where you want it in the markup and the browser takes care of the rest, cool right? There are a bunch of other such tags as you know, <a>, <p>, <img>, etc. They all work the same way.

Custom elements are a way for you to create such tags for your own purposes. You can create a tag that's called <my-cool-div> that the browser will recognize and stamp in the DOM the same way than the standard <div>. This cool div could do anything. For example, it could just be a normal div but that centers stuff horizontally AND vertically (imagine not having to search for that on stackoverflow ever again!). It would look like something like this:

  <my-cool-div>This text is centered</my-cool-div>

Great! How do you do that?

There are a couple of steps, first one is:

class MyCoolDivElement extends HTMLElement {}
window.customElements.define('my-cool-div', MyCoolDivElement)

This code does 2 things:

  • It creates a MyCoolDivElement class that extends HTMLElement, the native base class for all custom elements.
  • It registers the <my-cool-div> tag in the browser. This means that any <my-cool-div> occurences in the HTML markup will make the browser instanciate a MyCoolDivElement and stamp it in the DOM from now on.

Try it! Open your browser's console and write those two lines. Then do this:

const coolDiv = document.createElement('my-cool-div')
document.body.appendChild(coolDiv)

If you inspect this page's DOM, at the bottom of the body you'll find a DOM node of type <my-cool-div></my-cool-div>.

Be amazed!

But wait, where's the code for centering stuff like you said earlier?

We have to define this in what is called the web component's shadow DOM!

Shadow DOM

This is the most complicated concept, so please bear with me.

Welcome to the dark side

If you know about <div>, you may also know about <button>. Try adding one to the bottom of this page:

const someButton = document.createElement('button')
someButton.textContent = 'Press me'
document.body.appendChild(someButton)

Ever wondered why the default button is so ugly? Where does that style come from? Why is it different from one browser to another?

The answer lies within the Shadow DOM. It's a hidden subtree where you can add styles and other nodes to your custom element that won't be visible to the outside world. In other words: it encapsulates CSS and a DOM subtree.

For our button example, this means that browser makers, when implementing the <button> spec, decided to add an ugly default <style> to the button element. This style is defined in the HTMLButtonElement's shadow root and doesn't leak out of there. It may include other nodes like a couple of <div> or <p>, we don't know and don't need to know it, we're just consumers of <button>.

We're going to do the exact same thing and make <my-cool-div> center stuff by default.

The power of the dark side

We've already seen how to define our custom element, let's build on that and add the shadow root with the style we need:

class MyCoolDivElement extends HTMLElement {
  constructor() {
    super()
    this.attachShadow({ mode: 'open' })
    const style = document.createElement('style')
    style.textContent = `
      :host {
        display: flex;
        justify-content: center;
        align-items: center;
      }
    `

    this.shadowRoot.appendChild(style)
    const slot = document.createElement('slot')
    this.shadowRoot.appendChild(slot)
  }
}

window.customElements.define('my-cool-div', MyCoolDivElement)

To try it out in your browser, copy it in the console like before and then do:

const myCoolDiv = document.createElement('my-cool-div')
myCoolDiv.textContent = 'This text is centered'
myCoolDiv.style = 'width: 100%; height: 200px;'
document.body.appendChild(myCoolDiv)

At the bottom of this page, you should see the text well centered like how we wanted it to be!

Ok now let's roll back a bit and explain all that new stuff we added ìnto MyCoolDivElement's definition:

The constructor

class MyCoolDivElement extends HTMLElement {
  constructor() {
    super()
  }
}

This is standard javascript, we're just overriding the default constructor of the class and calling its parent's (HTMLElement) with super(). This is called whenever the browser reads a <my-cool-div> in some markup or when we call document.createElement('my-cool-div') manually.

Creating the shadow root

this.attachShadow({ mode: 'open' })

This creates the shadow root, where we'll be able to encapsulate the styles. You could ask questions about the mode: open thing, but it's beyond the scope of this post really.

Adding the styles

const style = document.createElement('style')
style.textContent = `
  :host {
    display: flex;
    justify-content: center;
    align-items: center;
  }
`

this.shadowRoot.appendChild(style)

This creates the style node and adds it in the shadow root. Notice the CSS :host selector: this is specific to shadow root styles, it targets the shadow root itself. More on that below.

Adding a slot

const slot = document.createElement('slot')
this.shadowRoot.appendChild(slot)

This is the hardest thing to figure out. This adds a kind of "hole" in the shadow root. This is where content that is passed to the element will be placed, in our case the text "I am centered". Since this slot is a child of our shadow root, it will be centered according to our shadow root flexbox properties! You may picture something like this:

  <shadow-root> <!-- <my-cool-div> -->
    <slot></slot> <!-- I am centered-->
  </shadow-root> <!-- </my-cool-div> -->

Voilà!

One shadow root to bring them all and in the darkness bind them

Now imagine we wanted every centered stuff to have a kind of thick blue line over it (because the designer said so). We could go about and do something like that:

class MyCoolDivElement extends HTMLElement {
  constructor() {
    super()
    this.attachShadow({ mode: 'open' })
    const style = document.createElement('style')
    style.textContent = `
      :host {
        display: flex;
        flex-direction: column;
        justify-content: center;
        align-items: center;
      }

      span {
        width: 50%;
        height: 4px;
        background-color: blue;
      }
    `

    this.shadowRoot.appendChild(style)

    const span = document.createElement('span')
    this.shadowRoot.appendChild(span)

    const slot = document.createElement('slot')
    this.shadowRoot.appendChild(slot)
  }
}

window.customElements.define('my-cool-div', MyCoolDivElement)

Wait, it's really bad to use general CSS selectors like that span {}! It will affect all the <span> in the page!

Well, not if this stylesheet is defined in the shadow root! Those styles are encapsulated inside the shadow root remember? They don't leak everywhere! Try adding a <span> next to a <my-cool-div> and you'll see it's not blue or anything:

const myCoolDiv = document.createElement('my-cool-div')
myCoolDiv.textContent = 'I have a blue line'
myCoolDiv.style = 'width: 100%; height: 200px;'
document.body.appendChild(myCoolDiv)

const span = document.createElement('span')
span.textContent = `I'm not a blue line`
span.style = 'width: 100%; height: 200px;'
document.body.appendChild(span)

Starting to see the magic yet? You could add multiple <my-cool-div> with a different text in it, or even other nodes. Everything will be centered with a blue line over it! Play with it a bit in this page and try adding some other stuff in the shadow root.

In the next part of this post, we'll see the two remaining core concepts behind native web components. We'll also use some tools to make our lives easier (because it's rather tedious to do all of this by hand).

Posted on by:

pdesjardins90 profile

Philippe Desjardins

@pdesjardins90

I'm a freelance software engineer, specialized in web development. I build web applications, APIs and automate enterprise processes with cloud services on AWS

Discussion

pic
Editor guide
 

You're amazing. Excellent explanation and I love the tone of your writing.

 

Thanks! Much appreciated 😃