WebComponents can be the salvation of Component-based web development.
Where all the front-end frameworks are pushing for Component approach and thinking in component style, DOM has the native way to address this. WebComponents is the collective solution to have components in the Browser natively. This collective solution includes:
- CustomElements
- ShadowDOM
- HTML Template
HTML Imports (Deprecated)
To get up and running with WebComponents, you only need the CustomElements V1 polyfill which provides a generic way to create components and lifecycle methods, which you can obtain from the following repository:
webcomponents / polyfills
Web Components Polyfills
Many would say that you will need shadowDOM
, template tags, HTML imports for your custom elements. They are right but not completely. You can create your components without them as well.
CustomElements
CustomElements are the elements similar to native HTML elements like div
, span
etc. These are the extension of HTMLElement
constructor and other similar constructors based on the type of CustomElement you wanna create.
Let's see an example; consider you wanna create a web component which will serve as a quick creation of figure
with img
and figcaption
together. Normally the HTML will look like the following:
<figure>
<img
src="https://developer.cdn.mozilla.net/media/img/mdn-logo-sm.png"
alt="An awesome picture">
<figcaption>MDN Logo</figcaption>
</figure>
The Example is taken from https://developer.mozilla.org/en-US/docs/Web/HTML/Element/figure
And the component will look like:
<img-figure
caption="MDN Logo"
alt="An awesome picture"
src="https://developer.cdn.mozilla.net/media/img/mdn-logo-sm.png"
></img-figure>
The basic component code will be as follows:
class ImgFigure extends HTMLElement {
connectedCallback() {
this.src = this.getAttribute("src") || null;
this.caption = this.getAttribute("caption") || "";
this.alt = this.getAttribute("alt") || null;
this.render();
}
render() {
this.innerHTML = this.template({
src: this.src,
alt: this.alt,
caption: this.caption
});
}
template(state) {
return `
<figure>
<img
src="${state.src}"
alt="${state.alt || state.caption}">
<figcaption>${state.caption}</figcaption>
</figure>
`;
}
}
customElements.define('img-figure', ImgFigure);
And its usage through JavaScript will be as follows:
// create element
const i = document.createElement('img-figure');
//set the required attributes
i.setAttribute('src', '//res.cloudinary.com/time2hack/image/upload/goodbye-xmlhttprequest-ajax-with-fetch-api-demo.png');
i.setAttribute('caption', 'GoodBye XMLHttpRequest; AJAX with fetch API (with Demo)');
i.setAttribute('alt', 'GoodBye XMLHttpRequest');
//attach to the DOM
document.body.insertBefore(i, document.body.firstElementChild);
Or Create the element right in DOM like as follows:
<img-figure
style="max-width: 400px"
src="//res.cloudinary.com/time2hack/image/upload/ways-to-host-single-page-application-spa-static-site-for-free.png"
alt="Free Static Hosting"
caption="Ways to host single page application (SPA) and Static Site for FREE">
</img-figure>
Demo:
Let's take a look at the component creation in detail:
Initial Required part
All custom elements/components extend the basic HTMLElement object and have the features of it like the attributes, styles etc.
class ImgFigure extends HTMLElement {
connectedCallback() {
// ....
}
}
And the connectedCallback
is executed when they are attached to the DOM. So we place the initial code in this function.
Final Required Part
Finally, we need to register the element to the DOM, so that when DOM sees that element, it will instantiate the above-mentioned Class rather than HTMLElement
.
customElements.define('img-figure', ImgFigure);
And that's it. These parts will register the component and available to be created through document.createElement
API.
Play with WebComponents (another Demo):
But what if you wanna use the make it react to any attribute change?
For that, there are two pieces of code that should be present in the Component's class.
One: Need to register the observable attributes:
static get observedAttributes() {
return ['attr1', 'attr2'];
}
And Second: Need to react on the observable attributes' changes:
attributeChangedCallback(attr, oldValue, newValue) {
if(oldValue === newValue){
return;
}
if (attr == 'attr1') {
// some stuff
}
if (attr == 'attr2') {
// some other stuff
}
}
Let's see these two pieces of code in our old img-frame
Component:
class ImgFigure extends HTMLElement {
connectedCallback() {
this.src = this.getAttribute('src') || null;
this.caption = this.getAttribute('caption') || '';
this.alt = this.getAttribute('alt') || null;
this.render();
}
static get observedAttributes() {
return ['src'];
}
attributeChangedCallback(attr, oldValue, newValue) {
if(oldValue === newValue){
return;
}
if (attr === 'src') {
this.querySelector('img').src = newValue;
}
}
render() {
this.innerHTML = template({
src: this.src,
alt: this.alt,
caption: this.caption,
});
}
}
This way you can create your custom elements without needing to worry about much of the browser support.
The life-cycle methods of the customElement
are:
Method | Usage/Description |
---|---|
constructor() | Called when the element is created or upgraded |
connectedCallback() | Called when the element is inserted into a document, including into a shadow tree |
disconnectedCallback() | Called when the element is removed from a document |
attributeChangedCallback(attrName, oldVal, newVal, namespace) | Called when an attribute is changed, appended, removed, or replaced on the element (Only called for observed attributes) |
adoptedCallback(oldDocument, newDocument) | Called when the element is adopted into a new document |
Support?
Can I Use custom-elementsv1? Data on support for the custom-elementsv1 feature across the major browsers from caniuse.com.
But wait! Firefox is right there to support customElements
:
Summary:
This is basically an after the fact notification that we're in progress of implementing Custom Elements (both autonomous custom elements and customized built-in elements) and the implementation for old spec, which was never exposed to the web, will be removed. We are close to finishing the implementation, but there are still some performance works before shipping the feature. We plan to enable it only on Nightly first to get more feedback from users. (there will be an intent-to-ship before shipping the feature)
https://groups.google.com/forum/#!msg/mozilla.dev.platform/BI3I0U7TDw0/6-W39tXpBAAJ
Detailed Reading on CustomElements: https://developers.google.com/web/fundamentals/web-components/customelements
But What about ShadowDOM?
ShadowDOM
ShadowDOM is a way to encapsulate the underlying DOM and CSS in a Web Component. So if you really need the encapsulation; cases when you are providing widgets to the third party; use ShadowDOM.
Primarily you can attach ShadowDOM with attachShadow
and then perform operations on it:
element.attachShadow({mode: 'open'});
Let's see an example of ShadowDOM:
The attachShadow
method needs a configuration object which says only about the encapsulation. The object will have key mode
which will have value either open
or closed
.
And as explained at https://developer.mozilla.org/en-US/docs/Web/API/Element/attachShadow:
mode
: A string specifying the encapsulation mode for the shadow DOM tree. One of:
element.shadowRoot === shadowroot; // returns true
closed
: Specifies closed encapsulation mode. This mode denies any access to node(s) of a closed shadow root from an outside world
element.shadowRoot === shadowroot; // returns false
element.shadowRoot === null; // returns true
The attachShadow
returns the ShadowRoot
which you can use as a regular document and perform operations on it.
Support?
Can I Use shadowdomv1? Data on support for the shadowdomv1 feature across the major browsers from caniuse.com.
More/Detailed reading on ShadowDOM: https://developers.google.com/web/fundamentals/web-components/shadowdom
HTML Template
The HTML templates provide the mechanism to send the markup on the page but not being rendered. This is a huge help if you wanna keep your JavaScript bundle size to minimal.
Once the template is on the document, it can be cloned and then filled with the relevant dynamic content with JavaScript
Its support is still not wide enough; so you can check that with following code
if ('content' in document.createElement('template')) {
// operate on the template
}
Considering that the browser being used supports the template tags; you can use them in the following way:
<template id="img-figure">
<figure>
<img />
<figcaption></figcaption>
</figure>
</template>
let template = () => `Template tag not supported`;
const t = document.querySelector('#img-figure');
if ('content' in document.createElement('template')) {
template = (state) => {
const img = t.content.querySelector('img');
const caption = t.content.querySelector('figcaption');
img.setAttribute('src', state.src);
img.setAttribute('alt', state.alt || state.caption);
caption.innerHTML = state.caption;
return document.importNode(t.content, true);
}
} else {
template = (state) => { //fallback case
const d = document.createElement('div');
d.innerHTML = t.innerHTML;
const img = d.querySelector('img');
const caption = d.querySelector('figcaption');
img.setAttribute('src', state.src);
img.setAttribute('alt', state.alt || state.caption);
caption.innerHTML = state.caption;
return d.firstElementChild;
}
}
class ImgFigure extends HTMLElement {
connectedCallback() {
this.src = this.getAttribute("src") || null;
this.caption = this.getAttribute("caption") || "";
this.alt = this.getAttribute("alt") || null;
this.render();
}
render() {
this.appendChild(template({
src: this.src,
alt: this.alt,
caption: this.caption,
}));
}
}
customElements.define('img-figure', ImgFigure);
Read more on HTML Template here: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/template
HTML Imports (Deprecated)
The HTML Imports are the easiest way to deliver the WebComponents to the desired location.
These work in the same way as you import external stylesheets in your document.
<link rel="import" href="img-figure.html" />
And then your component file img-figure.html
can have other dependency added, like as follows:
<link rel="stylesheet" href="bootstrap.css">
<script src="jquery.js"></script>
<script src="bootstrap.js"></script>
...
https://www.html5rocks.com/en/tutorials/webcomponents/imports/
Help
Following places will be able to help you more in understanding the concepts of WebComponents:
- https://developers.google.com/web/fundamentals/web-components/
- https://developer.mozilla.org/en-US/docs/Web/Web_Components/Custom_Elements
- https://developer.mozilla.org/en-US/docs/Web/Web_Components
People/Companies using WebComponents
To motivate you about WebComponents:
Others who are not very social 😉
https://github.com/Polymer/polymer/wiki/Who's-using-Polymer?
Final Thoughts
WebComponents are great. And then slowly all browsers are moving towards complete support.
You can use them with regular JavaScript script include as well if you are not sure about the support for HTML imports and template tags.
Special Thanks
Thanks a lot Alex and Nico for helping and reviewing this post:
Let us know what you think about the WebComponents via comments.
If you are stuck somewhere while implementing WebComponents, reach out through comments below and we will try to help.
Top comments (3)
I've never seen HTML Imports lumped in with specs that make up web components. It seems like both Chrome and Firefox are not implementing it so far.
developer.mozilla.org/en-US/docs/W...
Html imports are deprecated.
Agree.