Web Components: How to use web components?

When I first started working with Web Components I looked at the biggest way to use them. In creating my own components I own all of the component, the scripts, the encapsulated CSS and the responsibility of making sure that they worked and worked well with other components and other elements in the page.

In learning how to use Web Components we’ll look at both the big and the small picture: Creating full custom components and createying type extension custom elements.

Custom Components

The ability to create fully customized and reusable elements is what attracted me to Polymer and the concept of web components. The elements we create can be as simple or as complex as we need them to be…. We can also add other components to enhance the functionality of our components.
The example below (taken from the Polymer Project’s home page) shows what a custom element built with Polymer 1.0 looks like.

<dom-module id="contact-card">       
  <link rel="import" type="css" href="contact-card.css">
  <template>         
    <content></content>      
    <iron-icon icon="star" hidden$="{{!starred}}"></iron-icon>       
  </template>        
  <script>       
  Polymer({      
    is: 'contact-card',      
    properties: {
      starred: Boolean       
    }        
 });         
 </script>       
</dom-module>        

And how we use the component on our document.

<contact-card starred>       
 <img src="profile.jpg" alt="Eric's photo">      
 <span>Eric Bidelman</span>      
</contact-card>      

Extending existing elements (Type Extension Custom Elements)

There are times when a custom element is too much. We might need a smaller chunk of functionality or we may need to enhance an already-existing element instead of creating a whole new element. You can create a custom element that extends a native HTML element and its features. This is called a Type Extension Custom Element. To use the element, use the original tag and specify the custom tag name using the is attribute.

<input is="x-component"></div>       

To define a type extension:

  • Create the base prototype object using the prototype of the extended element, instead of HTMLElement.
  • Add an extends key in the second argument to document.registerElement(), specifying the tag name of the extended element.

Following is an example code when extending the input element:

var XComponent = document.registerElement('x-component', {       
 extends: 'input',       
 prototype: Object.create(HTMLInputElement.prototype)        
});      

Notice that it extends: ‘input’ and its prototype is based on HTMLInputElement instead of HTMLElement. Now you can use &lt;input is="x-component"> inside your document. By doing so, you can have extended APIs on top of basic input element’s features.

Github’s example

Github Relative Time on display
Github Relative Time on display

GitHub has a component that displays date and time as shown above. Notice they are not absolute dates/times but relative to the browser’s current time. GitHub uses a Type Extension Custom Element accomplish this. The HTML code looks like this:

HTML source for time type extension custom element
HTML source for time type extension custom element

There some things to notice:

  • time tag is used as a base element
  • datetime attribute indicates an absolute date/time
  • is='time-ago' specifies a type extension
  • The tag’s content indicates a relative date/time
  • This is done on the fly as a type extension.

Even if web components are not supported or Javascript is disabled we will still be able to see when the file was last changed. If you disable Javascript from your browser’s

For more details about time-elements, check webcomponents.org’s How GitHub is using Web Components in production.

Web Components: How to build web components?

To illustrate how to create web components we’ll use the same element for all three methods. The end result will look like the example below:

  <my-avatar service="twitter" username="caraya"></my-avatar>

We’ll look at the differences between a script based approach used in x-tags and a declarative take using Polymer.

Using vanilla Javascript

var MyAvatarPrototype = Object.create(HTMLElement.prototype);
MyAvatarPrototype.createdCallback = function() {
  var username = this.getAttribute('username');
  var service = this.getAttribute('service');
  var url = 'http://avatars.io/' + service + '/' + username;
  var img = document.createElement( 'img' );
  img.setAttribute('src', url);
  this.appendChild(img);
};
document.registerElement('my-avatar', {
  prototype: MyAvatarPrototype
});

or with tags and scripts:

<template id="myAvatar">
    <img src=" ">
</template>
<script>
function addAvatar(service, username) {
  var t = document.querySelector("#myAvatar");
  // This will stamp out the element into our DOM
  var comment = t.content.cloneNode(true);
  // Build the URL
  var serviceURL = '"http://avatar.io/" + service + "/" + username'
  // Populate content.
  comment.querySelector('img').src = serviceUrl;
  comment.querySelector('.comment-text').textContent = text;
  document.body.appendChild(comment);
}
</script>

X-Tags

X-tags implementation of Web Components lacks the capability to import elements; their justification is that they are waiting to see if Javascript modules will work to avoid standards and technologies that duplicate each other.

//<script type='text/javascript' src='scripts/x-tag-core.min.js'></script>    
//<script type='text/javascript'>
xtag.register('my-avatar', {
  // The 'content' property generates templated content
  // and automatically adds it to each new element you create
  content: '<div class="avatar"></div><img class="avatar" />',
    lifecycle: {
      created: function() { 
        this.xtag.img = this.lastElementChild;
        // In the next release we're adding default attribute values
        // so you won't have to do these if () checks
        if (!this.service) this.service = 'twitter';
        if (!this.username) this.username = 'name';
      }
  },
  accessors: {
    service: {
      attribute: {},
      set: function(val){
        this.xtag.img.src = 'http://avatars.io/' + val + '/' + this.username;
      }
    },
    username: {
      attribute: {},
      set: function(val){
        this.xtag.img.src = 'http://avatars.io/' + this.service + '/' + val;
      }
    }
  }
});

Polymer

Polymer has gotten more complicated since I last played with it in the 0.5 but it’s still fairly easy to implement and modify. We’ll explore some of the changes from 0.5 to 1.0 and some of the syntactic sugar that makes working with web components fun.

The example below is the Polymer version of our my-avatar tag.

<dom-module id="my-avatar">
  <style>
    .avatar {
      border: 0;
    }
  </style>
  <template>
    <img class='avatar' src='http://avatars.io/{{service}}/{{username}}'>
  </template>
</dom-module>
<script>
  'use strict';
  Polymer({
    is: 'my-avatar',
    properties: {
      service: {
        type: String,
        value: '',
        notify: true
      },
      value: {
        type: String,
        value: '',
        notify: true
      }
    }
  });
</script>

There is a very interesting presentation from ForwardsJS 3 that covers why you should be using web components now.

Web Components: Why not use web components?

Wilson Page wrote a lengthy article in Mozilla Hacks about the state of web components. What I found most interesting about the piece are the reasons why web components have not reached recommendation status in the W3C. Quoting the article:

By now, 4 years on, Web Components should be everywhere, but in reality Chrome is the only browser with ‘some version’ of Web Components. Even with polyfills it’s clear Web Components won’t be fully embraced by the community until the majority of browsers are on-board.

Why has this taken so long?

To cut a long story short, vendors couldn’t agree.

Web Components were a Google effort and little negotiation was made with other browsers before shipping. Like most negotiations in life, parties that don’t feel involved lack enthusiasm and tend not to agree.

Web Components were an ambitious proposal. Initial APIs were high-level and complex to implement (albeit for good reasons), which only added to contention and disagreement between vendors.

Google pushed forward, they sought feedback, gained community buy-in; but in hindsight, before other vendors shipped, usability was blocked.

Polyfills meant theoretically Web Components could work on browsers that hadn’t yet implemented, but these have never been accepted as ‘suitable for production’.

Aside from all this, Microsoft haven’t been in a position to add many new DOM APIs due to the Edge work (nearing completion). And Apple, have been focusing on alternative features for Safari.

When I look at the article outlining alternative proposals and opposition to the concept as specified and currently implemented and polyfilled makes me wonder if this is another example of bike shedding.

If accessibility is an issue, then perhaps you’d be better off without web components until some of the issues outline by Dominic in his web component accessibility analysis are resolved. These issue will vary depending on the library that you choose to use.

Browser adoption and polyfill use are elements to consider. Even though polyfill exist for web components, the polyfills themselves introduce additional complications. How do the polyfills interact with other libraries in your application and viceversa. There used to be ways to pierce shadow boundaries so developers could style components from the ‘host’ application but that is being worked on or may have already been deprecated.

The situation has improved but it’s still not ideal. Most browsers have implemented some of the Web Components specifications or have indicated that they are working on implementation. This disparity in development means that we still need to make the webcomponents.js polyfills available for our applications.

Not all web components specifications can be polyfilled and, although performance has improved, it may still be an issue for mobile devices.

The final decision about using web components is necessarily yours. There are companies that have deployed production applications using Polymer 0.5 and 1.0. Whether the trade off between performance and ease of use is worth it will depend on your needs.

Web Components: Why use web components?

I can think of a few reasons why we should build and use web components.

Clean and DRY Code

If you look at the source code of any large web application you’ll see that it’s a mess of divs and enough tags to make it really hard to follow.

Gmail looks bad in the sense that the source is almost impossible to follow and understand, the class names only make sense to the people developing the application and, most likely, no one else.

Screenshot of browser showing source code for Gmail
Source code for Gmail

So does Facebook

Screenshot of browser view source showing Source code for Facebook
Screenshot of browser view source showing Source code for Facebook

What if we could change all the ‘div soup’ into something that makes more sense and it’s eaier to read?

Using Polymer we can define a fictional company-module tag with the following information and using an existing iron-icon component (downloadable from the Polymer site):

<dom-module id="company-info">
  <link rel="import" type="css" href="company-info.css">
  <template>
    <content></content>
    <iron-icon icon="star" hidden$="{{!starred}}"></iron-icon>
  </template>
  <script>
    Polymer({
      is: 'company-info',
      properties: {
        starred: Boolean
      }
    });
  </script>
</dom-module>

And then instantiate the element for company MYX like so:

<company-info starred>
  <img src="company-logo.jpg" alt="company logo for MYX">
  <span>MYX Corporation</span>
</company-info>

Using vanilla web components, we can define a my-avatar tag that looks like this:

<my-avatar service="twitter" username="caraya"></my-avatar>

We can then use Javascript to define the attributes necessary for the custom element to work. The code looks like this.

var MyAvatarPrototype = Object.create(HTMLElement.prototype);
MyAvatarPrototype.createdCallback = function() {
  var username = this.getAttribute('username');
  var service = this.getAttribute('service');
  var url = 'http://avatars.io/' + service + '/' + username;
  var img = document.createElement( 'img' );
  img.setAttribute('src', url);
  this.appendChild(img);
};
document.registerElement('my-avatar', {
  prototype: MyAvatarPrototype
});

It is clear to read, you can immediately see what each portion is supposed to do and how they all fit together. As a content creator you don’t need to worry about the backend stuff, that’s still transparent to you.

Interoperability

Another thing to consider is interoperability. Right now all major CSS frameworks have their own implementations for things such as button and if you want to use Bootstrap’s button in a Foundation app, it’s just not worth it, you have to add Bootstrap’s bloat to foundation’s bloat without knowing how will the CSS and Javascript from each framework interact with other scripts and style sheets.

Web components cut the likelihood of interoperability issues.

Because Shadow DOM hides the CSS on your component from the host page, we don’t need to worry about the component styles affecting the page and vice versa. There are ways to pierce the boundary between host and components but that will most definitely change before Shadow DOM becomes a recommendation.

Vanilla JS components, X-Tags / Bricks and Polymer web components

Composability

Since we can put components from multiple sources or multiple components from a single source, we can compose an application from smaller single-purpose components and compose a more complex application from them.

Because components are composable and Google provides a large catalog of components for people to build our applications with, we can concentrate on our own application specific or task specific components and build larger applications without having to reinvent the wheel every time (also known as keeping our code DRY)

Web Components: What are web components?

Web Components are a set of technologies that allow developers to create custom tags for their web projects. The technologies that make up web components are:

  • Custom Elements allows you to create custom tags for your project-specific needs. These can be brand new tags like book-index or extensions of existing elements with added UI or functionality (super-button or footnote-link)
  • HTML Templates defines a template for your new element. These templates are inert until you instantiate your elements in your page
  • Shadow DOM encapsulates CSS to your custom element. This makes the styles in your components independent of your main document. It has proved to be one of the most problematic aspects of web components but I still think that it’s the most useful… if it wasn’t then browser vendors wouldn’t use it in sliders and video elements
  • HTML Imports extends the link element that we use to import CSS style sheets to import web components stored in an HTML element using a similar syntax like so: <link rel="import" href="/path/to/some/import.html"/>

By themselves each standard under the umbrella of web components is awesome but it’s the combination of these technologies that has really deep implications for web developers everywhere.

The first way to build web components is to use the bare specifications: custom elements, templates, HTML imports and shadow DOM. This is the most direct yet most complex way to build components. Both X-Tags and Polymer abstract the complexities from the specifications as we’ll see below. For more information on web components check the Introduction to Web Components from the S3C

X-Tags provides a partial implementation of web components. Mozilla believes that ES6 modules will be a better solution than HTML imports for the same task. See https://developer.mozilla.org/en-US/Apps/Tools_and_frameworks/Web_components for more information on X-Tags (the library) and Bricks (the components built with X-Tags)

Polymer provides the basics of web components and opinionated sugar on top of web components to make building applications easier.

One thing to note is that, if you’ve worked with Polymer before 1.0, Polymer has changed considerably from prior versions. See the 1.0 release blog post for more information about changes and how to get started and Road to Polymer for ideas and processes on upgrading existing Polymer apps.