Web Components Frameworks Overview
Alexander Tchitchigin
Positive Technologies
Web Components
- Custom Elements
- Shadow DOM
- HTML Templates
- ES6 Modules
Custom Elements
class FlagIcon extends HTMLElement {
constructor() {
super();
this._countryCode = null;
}
static get observedAttributes() { return ["country"]; }
attributeChangedCallback(name, oldValue, newValue) {
// name will always be "country" due to observedAttributes
this._countryCode = newValue;
this._updateRendering();
}
connectedCallback() {
this._updateRendering();
}
get country() {
return this._countryCode;
}
set country(v) {
this.setAttribute("country", v);
}
_updateRendering() {
// Perform the rendering using Shadow DOM and/or HTML Templates
}
}
customElements.define("flag-icon", FlagIcon);Shadow DOM
class MyElement extends HTMLElement {
static get observedAttributes() {
return ['disabled'];
}
constructor() {
const shadowRoot = this.attachShadow({mode: 'open'});
shadowRoot.innerHTML = `
<style>
.disabled {
opacity: 0.4;
}
</style>
<div id="container"></div>
`;
this.container = this.shadowRoot('#container');
}
attributeChangedCallback(attr, oldVal, newVal) {
if(attr === 'disabled') {
if(this.disabled) {
this.container.classList.add('disabled');
}
else {
this.container.classList.remove('disabled')
}
}
}
}HTML Templates
class MyElement extends HTMLElement {
...
constructor() {
const shadowRoot = this.attachShadow({mode: 'open'});
this.shadowRoot.innerHTML = `
<template id="view1">
<p>This is view 1</p>
</template>
<template id="view1">
<p>This is view 1</p>
</template>
<div id="container">
<p>This is the container</p>
</div>
`;
}
connectedCallback() {
const content = this.shadowRoot.querySelector('#view1').content.clondeNode(true);
this.container = this.shadowRoot.querySelector('#container');
this.container.appendChild(content);
}
}Frameworks
Polymer
- The Elder One
- From Google
- Completely rewritten
- Tools and Libraries
- TypeScript and Redux
Polymer
export class HnUserElement extends connect(store)(LitElement) {
static get styles() {
return [
sharedStyles,
css`
table {
margin: 1em 0;
}`
];
}
render() {
const user = this._user || {};
return html`
<hn-loading-button .loading="${user.isFetching}" @click="${this._reload}">
</hn-loading-button>
<table ?hidden="${user.failure}">
<tr>
<td>User:</td>
<td>${user.id}</td>
</tr>
<tr>
<td>Created:</td>
<td>${user.created}</td>
</tr>
<tr>
<td>Karma:</td>
<td>${user.karma}</td>
</tr>
</table>
${user.failure ? html`<p>User not found</p>` : null}`;
}
@property()
private _user?: UserState;
_reload() {
store.dispatch(fetchUser(this._user));
}
stateChanged(state: RootState) {
const user = currentUserSelector(state);
if (user) {
updateMetadata({ title: user.id });
this._user = user;
}
}
}
customElements.define('hn-user', HnUserElement);Polymer

LitElement
- Powers Polymer
- From Google
- Light(er) weight
- TypeScript and all
- Embeddable
- No state management
LitElement
import {LitElement, html, css, customElement, property} from 'lit-element';
@customElement('my-element')
export class MyElement extends LitElement {
// This decorator creates a property accessor that triggers rendering and
// an observed attribute.
@property()
mood = 'great';
static styles = css`
span {
color: green;
}`;
// Render element DOM by returning a `lit-html` template.
render() {
return html`Web Components are <span>${this.mood}</span>!`;
}
}Stencil
- "A toolchain for building Design Systems"
- From Ionic Framework
- One-way data binding
- TypeScript, router, redux
- Uses JSX
Stencil
import { Component, Prop } from '@stencil/core';
@Component({
tag: 'my-first-component',
})
export class MyComponent {
// Indicate that name should be a public property on the component
@Prop() name: string;
render() {
return (
<p>
My name is {this.name}
</p>
);
}
}Skate.js
- Functional reactive abstraction over view technology
- Supports React, Preact, lit-html
- No state management
- Embeddable
Skate.js
import Element, { html } from '@skatejs/element-lit-html';
export default class extends Element {
static props = {
name: String
};
render() {
return html`
Hello, ${this.name}!
`;
}
}Slim.js
- Not a framework :)
- Lightweight, embeddable
- One-way data bindings
- Templates
Slim.js
import { tag, template, useShadow } from "slim-js/Decorators";
import { Slim } from "slim-js";
@tag('todo-item')
@template(/*html*/`
<style>
${require('./todo-item.css')}
</style>
<li bind:class="getClass(data.checked)">
<input s:id="checkbox"
bind.boolean:checked="data.checked"
type="checkbox" change="onChecked" />
<label>{{data.text}}</label>
<button click="onRemove">x</button>
</li>
`)
@useShadow(true)
export default class TodoItem extends Slim {
onChecked () {
this.checked = this.data.checked = this.checkbox.checked
this.commit()
}
getClass (checked) {
return checked ? 'completed' : ''
}
onRemove (e) {
this.callAttribute('on-remove', this.data)
}
}References
Thank you!
Questions?
Web Components Frameworks Overview
By Alexander Letov
Web Components Frameworks Overview
A brief overview of Web Components and several frameworks built on top of them.
- 81