Write JavaScript like a pro. Javascript Icon

Follow the ultimate JavaScript roadmap.

Web Components and concepts ShadowDOM imports templates custom elements

Web Components, the future of the web, inspired from attending Google I/O I decided to pick up Web Components and actually build something. Since learning the basics around a year ago, it’s changed and advanced a lot! Thought I’d write a post on it and share my first web component yesterday (built with Polymer).

Before I get into Polymer, we’ll look at Web Components in this post, what it means for the web and how it completely changes things and our outlook on building for the web platform from today.

Gone are the days of actually creating HTML structures and “pages” (what’re those?). The web is becoming “all about components”, and those components are completely up to us thanks to Web Components.

We aren’t really at a stage where we can use Web Components to its fullest, browser support is still ongoing implementations and IE are in consideration of the entire spec (blows a single fanfare). But it’s coming together, give it a few years and we’ll get there. Or do we have to wait that long?…

Google are innovating in this area like no tomorrow with Polymer.js, a polyfill and platform (that provides additional features such as data-binding, event callbacks and much more) for those missing pieces in modern browsers that don’t fully support Web Components.

Building blocks of Web Components

Before we get over excited about this stuff though, let’s actually understand what the Web Components spec really means. First thing’s first, Web Components are a collection of building blocks, not a single thing. Let’s look at each block to see what’s up.

This will be a very high level view, otherwise this post could end up being three days worth of reading!

Templates

Templates are where we define reusable code, we even get an element for it with <template>. The first time you use it, don’t panic - it’s invisible in the visible interface output, until you view source you won’t know anything is even there. It’s merely a declarative element to create a new template for… anything you like.

An example of a <template> to populate a profile section for a user:

<template id="profileTemplate">
  <div class="profile">
    <img src="" class="profile__img">
    <div class="profile__name"></div>
    <div class="profile__social"></div>
  </div>
</template>

Sprinkle some JavaScript to populate it, and append it to the <body>:

var template = document.querySelector('#profileTemplate');
template.querySelector('.profile__img').src = 'toddmotto.jpg';
template.querySelector('.profile__name').textContent = 'Todd Motto';
template.querySelector('.profile__social').textContent = 'Follow me on Twitter';
document.body.appendChild(template);

You’ll notice that this is just JavaScript, no new APIs or anything confusing. Nice! For me, a <template> is useless without its good buddy Custom Elements. We need this to do something useful with the tech, things are all global and disgusting as of now.

Custom Elements

Custom Elements allow us to define (you guessed it), our own element. This can be anything, but before you go crazy, your elements must have a dash, presumably to avoid any potential naming clashes with future HTML implementations - I think that’s a good idea as well.

Angular Directives In-Depth eBook Cover

Free eBook

Directives, simple right? Wrong! On the outside they look simple, but even skilled Angular devs haven’t grasped every concept in this eBook.

  • Green Tick Icon Observables and Async Pipe
  • Green Tick Icon Identity Checking and Performance
  • Green Tick Icon Web Components <ng-template> syntax
  • Green Tick Icon <ng-container> and Observable Composition
  • Green Tick Icon Advanced Rendering Patterns
  • Green Tick Icon Setters and Getters for Styles and Class Bindings

So, with our custom element, how do we do it? Simple really, we get the <element> element, so meta. Well, we had the <element> element. Read on, as <element> was recently deprecated and thus needs a JavaScript implementation, but this is the older way:

<element>
  <template id="profileTemplate">
    <div class="profile">
      <img src="" class="profile__img">
      <div class="profile__name"></div>
      <div class="profile__social"></div>
    </div>
  </template>
</element>

This example is still deprecated but worth showing. We would’ve given <element> a name="" attribute to define the custom element:

<element name="user-profile">
  <template id="profileTemplate">
    <div class="profile">
      <img src="" class="profile__img">
      <div class="profile__name"></div>
      <div class="profile__social"></div>
    </div>
  </template>
</element>

// usage
<user-profile></user-profile>

So what’s replacing <element>?

Use of <element> was deprecated towards the end of 2013, which means we simply use the JavaScript API instead, which I think offers more flexibility and less bloat on the markup:

<template id="profileTemplate">
  <div class="profile">
    <img src="" class="profile__img">
    <div class="profile__name"></div>
    <div class="profile__social"></div>
  </div>
</template>
<script>
var MyElementProto = Object.create(HTMLElement.prototype);
window.MyElement = document.registerElement('user-profile', {
  prototype: MyElementProto
  // other props
});
</script>

New elements must inherit from the HTMLElement.prototype. More on the above setup and callbacks etc here, cheers Zeno.

Extending and inheriting

What if we wanted to extend an existing element, such as an <h1> tag? There will be many cases of this, such as riding off an existing element and creating a “special” version of it, rather than a totally new element. We introduce the { extends: '' } property to declare where what element we’re extending. Using an extended element is simple, drop the is="" attribute on an existing element and it’ll inherit its new extension. Pretty simple, I guess.

<template>
  // include random, funky things
</template>
<script>
var MyElementProto = Object.create(HTMLElement.prototype);
window.MyElement = document.registerElement('funky-heading', {
  prototype: MyElementProto,
  extends: 'h1' // extends declared here
});
</script>

<h1 is="funky-heading">
  Page title
</h1>

Using extends="" as an attribute on <element> was the way to do it before it was deprecated.

So what next? Enter the shadows…

ShadowDOM

ShadowDOM is as cool as it sounds, and provides a DOM encapsulation within DOM. Whaaat? Essentially, nested document fragments, that are shadow-y… In ShadowDOM, we’re observing nested DOM trees/hierarchies. Typically in web documents, there is one DOM. Think about DOM hosting DOM, which hosts more DOM. You’ll see something like this in Chrome inspector (note #shadow-root, which is completely encapsulated DOM):

<user-profile>
  ▾#shadow-root (user-agent)
  <div class="profile">
    <img src="" class="profile__img">
    <div class="profile__name"></div>
    <div class="profile__social"></div>
  </div>
 </user-profile>

There are a few different concepts with Shadow DOM, for me, it’s that there is no “global” Object, no window, I can create a new document root. The “host” of my this new document root is either referred to as the root or host. We can create new ShadowDOM by invoking .createShadowRoot(); on an element.

ShadowDOM already exists in the wild today though, as soon as you use <input type=range> in the browser, we get a nice input with a slider, guess what - that’s ShadowDOM! It’s a nested structure that’s hidden inside our DOM tree. Now we can create it ourselves, this opens up an entire plethora of opportunities.

Why is this really cool?

ShadowDOM gives us true encapsulation, with scoped components. CSS is scoped (wow, although we tried this with <style scoped> but Blink have since removed it from the core to make way for Web Components). This means any CSS we write inside ShadowDOM only affects the DOM of that particular ShadowDOM!

<template>
  <style>
  :host {
    border: 1px solid red;
  }
  </style>
  // stuff
</template>
<script>
var MyElementProto = Object.create(HTMLElement.prototype);
window.MyElement = document.registerElement('funky-heading', {
  prototype: MyElementProto,
  extends: 'h1'
});
</script>

This also means each document can also have a unique id, and we can avoid crazy naming conventions for scaling our apps/websites (a minor bonus).

We can also put scripts in there too and talk to the current element:

 <template>
  <style>
  :host {
    border: 1px solid red;
  }
  </style>
  // stuff
</template>
<script>
(function () {
  // stuff with JS...
})();

var MyElementProto = Object.create(HTMLElement.prototype);
window.MyElement = document.registerElement('funky-heading', {
  prototype: MyElementProto,
  extends: 'h1'
});
</script>

JavaScript events that are fired, also are encapsulated to the ShadowDOM tree.

How can I see this ShadowDOM?

In true shadow style, you need to enable it via the Show user agent ShadowDOM checkbox inside Chrome Dev Tools. Upon inspecting element, you can see the nested DOM trees. Chrome also allows you to edit the CSS, which is even more awesome.

HTML Imports

Importing dependencies into our language of choice comes in many shapes and sizes. For CSS, we have @import, for JavaScript in ES6 modules we have import {Module} from './somewhere';, and finally, HTML. We can import HTML components at the top of our document to define which ones we need to use in our app:

<link rel="import" href="user-profile.html">

<!-- 
  <user-profile> now available, ooo yeah!
-->

This is massive! Encapsulated components all in one file. Out of the box and working. Let’s take Google Maps API for example, we need to include the Maps API v3, import the ‘Hello world’ code and then style a basic map. Wouldn’t it be great to just do this:

<link rel="import" href="google-map.html">

<!-- boom! -->
<google-map></google-map>

All encapsulated, tested, I could just pass in values via attributes and job done:

<google-map coords="37.2350, 115.8111"></google-map>

Decorators

Decorators are part of Web Components, but actually have no spec (according to the spec). Apparently they might look something like this, with their intention to enhance or override the presentation of an existing element. So ignore them for now, I guess (see Addy’s comment on Decorators, they might even disappear from Web Components entirely).

<decorator id="details-open">
  <template>
    <a id="summary">
      &blacktriangledown;
      <content select="summary"></content>
    </a>
    <content></content>
  </template>
</decorator>

Can I get started now? Enter Polymer.js

Yes. Web Components are going to be a little while before fully landing and being the next generation of the web, but they’re certainly making fast traction. We can get to grips with the technology and concepts now and start building using a framework such as Polymer - which polyfills things for modern browsers to let us use Web Components now.

An example of using Polymer to define an element. Here, we simply swap out (was) <element> for <polymer-elememt> and that’s it.

<polymer-element name="my-element">
  <template>
    // take it away!
  </template>
  <script>
    Polymer('my-element', {});
  </script>
</polymer-element>

<my-element></my-element>

Polymer has some really sweet features, such as data-binding (the Angular dev inside me loves this) and a tonne of simple events built in, from new instances of the element, to creation and injection callbacks that make it really simple to creating new elements.

Takeaways

This post isn’t meant to be a full tutorial - these components are vast and best explored individually, but I wanted to provide an eye opener on the rapidly approaching technology that is Web Components.

For me, one of the biggest selling points of Web Components is to prevent the inclusion of a huge JavaScript file, a huge CSS file and a tonne of HTML to make our website or app. In such cases, we no doubt come back to it a few months later and have forgotten what each thing does and it’s painful to get back up to speed again. We don’t forget what the <google-map> element does though, or the <fluid-vids> element, they’re declarative and self-explanatory, we know exactly where their logic is, and where the styles are.

The biggest win? Logic is contained. We’ve all struggled managing logic, markup and styles, and now the web has listened. Encapsulated behaviour and scoping, but a very powerful engine for componentising the web, anything from a navigation to google maps to an image slider.

Benefits of Web Components are very clear, and I’m interested to see where it takes us in the next few years. This post is by far from exhaustive, but I feel we should all take a dive into what the future of the web will bring us, we’ll be there sooner than you think!

Links to definitely keep an eye on (any others feel free to share below):

Learn JavaScript the right way.

The most complete guide to learning JavaScript ever built.
Trusted by 82,951 students.

Todd Motto

with Todd Motto

Google Developer Expert icon Google Developer Expert

Related blogs 🚀

Free eBooks:

Angular Directives In-Depth eBook Cover

JavaScript Array Methods eBook Cover

NestJS Build a RESTful CRUD API eBook Cover