Presentation about the native browser way for building web components. We look at examples and the pros and cons of doing it natively and using a library. At the end we look at the Angular way of wrapping custom components into Custom Elements.
2. A B O U T M E
{
"name": "Ilia Idakiev",
"experience": [
"Developer & Founder of HNS/HG",
"Lecturer in Advanced JS @ SoďŹa University",
"Contractor / Consultant",
"Public / Private Courses"
],
"involvedIn": [
"Angular SoďŹa", "SoďŹaJS", "BeerJS",
]
}
3. WEB COMPONENTS EVERYWHERE
SEPARATION OF CONCERNS (SOC)
⸠Design principle for separating a computer program into distinct sections, such
that each section addresses a separate concern. (Modularity)
4. WEB COMPONENTS EVERYWHERE
S.O.L.I.D PRINCIPLES OF OBJECT-ORIENTED PROGRAMMING
⸠Single Responsibility Principle
⸠Open / Close Principle
⸠Liskov Substitution Principle
⸠Interface Segregation Principle
⸠Dependency Inversion Principle
http://aspiringcraftsman.com/2011/12/08/solid-javascript-single-responsibility-principle/
5. WEB COMPONENTS EVERYWHERE
WEB COMPONENTS
⸠Introduced by Alex Russell (Chrome team @ Google) â¨
at Fronteers Conference 2011
⸠A set of features currently being added by the W3C to
the HTML and DOM speciďŹcations that allow the creation of
reusable widgets or components in web documents and web applications.
⸠The intention behind them is to bring component-based software
engineering to the World Wide Web.
6. WEB COMPONENTS EVERYWHERE
WEB COMPONENTS FEATURES:
⸠HTML Templates - an HTML fragment is not rendered, but stored until it is
instantiated via JavaScript.
⸠Shadow DOM - Encapsulated DOM and styling, with composition.
⸠Custom Elements - APIs to deďŹne new HTML elements.
⸠HTML Imports - Declarative methods of importing HTML documents into other
documents. (Replaced by ES6 Imports).
9. WEB COMPONENTS EVERYWHERE
(function () {
const template = createTemplate('<div>Hello World<div>');
class Counter extends HTMLElement {
constructor() {
super();
const shadowRoot = this.attachShadow({ mode: 'open' });
shadowRoot.appendChild(template.content.cloneNode(true));
}
}
customElements.define('hg-counter', Counter);
}());
DEFINE CUSTOM ELEMENT Create a new class that extends HTMLElement
counter.js
10. WEB COMPONENTS EVERYWHERE
(function () {
const template = createTemplate('<div>Hello World<div>');
class Counter extends HTMLElement {
constructor() {
super();
const shadowRoot = this.attachShadow({ mode: 'open' });
shadowRoot.appendChild(template.content.cloneNode(true));
}
}
customElements.define('hg-counter', Counter);
}());
DEFINE CUSTOM ELEMENT Register the new custom element.
counter.js
11. WEB COMPONENTS EVERYWHERE
HTML TEMPLATES
⸠The <template> tag holds its content hidden from the client.
⸠Content inside a <template> tag will be parsed but not rendered.
⸠The content can be visible and rendered later by using JavaScript.
12. WEB COMPONENTS EVERYWHERE
WAYS TO CREATE A TEMPLATE
<template id="template">
<h2>Hello World</h2>
</template>
const template =
document.createElement('template');
template.innerHTML =
'<h2>Hello World</h2>';
Using HTML Using JavaScript
13. WEB COMPONENTS EVERYWHERE
CREATE TEMPLATE HELPER FUNCTION
function createTemplate(string) {
const template = document.createElement('template');
template.innerHTML = string;
return template;
}
utils.js
14. WEB COMPONENTS EVERYWHERE
CREATE THE TEMPLATE
(function () {
const template = createTemplate('<div>Hello World<div>');
class Counter extends HTMLElement {
constructor() {
super();
const shadowRoot = this.attachShadow({ mode: 'open' });
shadowRoot.appendChild(template.content.cloneNode(true));
}
}
customElements.define('hg-counter', Counter);
}());
Use the create template helper function.
counter.js
15. WEB COMPONENTS EVERYWHERE
SHADOW DOM
⸠Isolated DOM - The component's DOM is self-contained
(e.g. document.querySelector() won't return nodes in the component's shadow DOM).
⸠Scoped CSS - CSS deďŹned inside shadow DOM is scoped to it. Style rules
don't leak out and page styles don't bleed in.
⸠Composition - done with the <slot> element.â¨
(Slots are placeholders inside your component that users can ďŹll with their own markup).
16. WEB COMPONENTS EVERYWHERE
DEFINE CUSTOM ELEMENT
(function () {
const template = createTemplate('<div>Hello World<div>');
class Counter extends HTMLElement {
constructor() {
super();
const shadowRoot = this.attachShadow({ mode: 'open' });
shadowRoot.appendChild(template.content.cloneNode(true));
}
}
customElements.define('hg-counter', Counter);
}());
counter.js
Utilise the class constructor.
27. WEB COMPONENTS EVERYWHERE
CUSTOM ELEMENTS LIFECYCLE CALLBACKS
⸠connectedCallback - Invoked each time the custom element is appended into a
document-connected element. This will happen each time the node is moved, and
may happen before the element's contents have been fully parsed.
⸠disconnectedCallback - Invoked each time the custom element is disconnected
from the document's DOM.
⸠attributeChangedCallback - Invoked each time one of the custom element's
attributes is added, removed, or changed.
⸠adoptedCallback - Invoked each time the custom element is moved to a new
document.
28. WEB COMPONENTS EVERYWHERE
CUSTOM COMPONENT ATTRIBUTES
index.html
class Counter extends HTMLElement {
static get observedAttributes() {
return ['value'];
}
attributeChangedCallback(name, oldValue, newValue) {
if (name === 'value') {
this.counter = newValue;
}
}
constructor() {
Handle attribute changes
29. WEB COMPONENTS EVERYWHERE
CUSTOM COMPONENT ATTRIBUTES
index.html
class Counter extends HTMLElement {
static get observedAttributes() {
return ['value'];
}
attributeChangedCallback(name, oldValue, newValue) {
if (name === 'value') {
this.counter = newValue;
}
}
constructor() {
DeďŹne which attributes should be watched
32. WEB COMPONENTS EVERYWHERE
WEB COMPONENT CSS
⸠:host - selects the shadow host of the shadow DOM
⸠:host() - match only if the selector given as the function's parameter matches
the shadow host.
⸠:host-context() -  match only if the selector given as the function's parameter
matches the shadow host's ancestor(s) in the place it sits inside the DOM
hierarchy.
⸠::slotted() - represents any element that has been placed into a slot inside an
HTML template
37. WEB COMPONENTS EVERYWHERE
BENEFITS OF USING CUSTOM COMPONENTS
⸠Framework agnostic - Written in JavaScript and native to the browser.
⸠SimpliďŹes CSS - Scoped DOM means you can use simple CSS selectors, more
generic id/class names, and not worry about naming conďŹicts.
⢠Productivity - Think of apps in chunks of DOM rather than one large (global) page.
⸠Productivity - Think of apps in chunks of DOM rather than one large (global)
page.
39. WEB COMPONENTS EVERYWHERE
COSTS OF USING CUSTOM COMPONENTS
⸠Template Generation - manually construct the DOM for our templates â¨
(No JSX features or Structural Directives)
⸠DOM Updates - manually track and handle changes to our DOMâ¨
(No Virtual DOM or Change Detection)
40. WEB COMPONENTS EVERYWHERE
LIT HTML
⸠Library Developed by the Polymer Team @ GOOGLE
⸠EfďŹcient - lit-html is extremely fast. It uses fast platform features like
HTMLÂ <template>Â elements with native cloning.
⸠Expressive - lit-html gives you the full power of JavaScript and functional programming
patterns.
⸠Extensible - Different dialects of templates can be created with additional features for setting
element properties, declarative event handlers and more.
⸠It can be used standalone for simple tasks, or combined with a framework or component model,
like Web Components, for a full-featured UI development platform.
⸠It has an awesome VSC extension for syntax highlighting and formatting.
44. WEB COMPONENTS EVERYWHERE
FRAMEWORKS
⸠StencilJS - a simple library for generating Web Components and
progressive web apps (PWA). â¨
(built by the Ionic Framework team for its next generation of performant mobile and desktop Web
Components)
⸠Polymer - library for creating web components.â¨
(built by Google and used by YouTube, NetďŹix, Google Earth and others)
⸠SkateJS - library providing functional abstraction over web
components.
⸠Angular Elements
45. WEB COMPONENTS EVERYWHERE
WEB COMPONENTS SERVER SIDE RENDERING
⸠SkateJS SSR - @skatejs/ssr is a web component server-side
rendering and testing library. (uses undom)
⸠Rendertron - Rendertron is a headless Chrome rendering solution
designed to render & serialise web pages on the ďŹy.
⸠Domino - Server-side DOM implementation based on Mozilla's
dom.js
47. WEB COMPONENTS EVERYWHERE
ANGULAR ELEMENTS
⸠A part of Angular Labs set.
⸠Angular elements are Angular components
packaged as Custom Elements.
48. WEB COMPONENTS EVERYWHERE
ANGULAR ELEMENTS
⸠Angularâs createCustomElement() function transforms an Angular component,
together with its dependencies, to a class that is conďŹgured to produce a self-
bootstrapping instance of the component.
⸠It transforms the property names to make them compatible with custom
elements.
⸠Component outputs are dispatched as HTML Custom Events, with the name
of the custom event matching the output name.
⸠Then customElements.deďŹne() is used to register our custom element.
Transformation
50. WEB COMPONENTS EVERYWHERE
NEW ANGULAR PROJECT
ng new angular-elements // Create new Angular CLI project
cd angular-elements
ng add @angular/elements // Add angular elements package
ng g c counter // Generate a new component
// Navigate to our new project
51. WEB COMPONENTS EVERYWHERE
ANGULAR ELEMENTS The generated component
@Component({
selector: 'app-counter',
templateUrl: './counter.component.html',
styleUrls: ['./counter.component.css'],
encapsulation: ViewEncapsulation.Native
})
export class CounterComponent {
@Input() counter = 0;
constructor() { }
inc() { this.counter++; }
dec() { this.counter--; }
}
src/app/counter/counter.component.ts
52. WEB COMPONENTS EVERYWHERE
ANGULAR ELEMENTS Add View Encapsulation via Shadow DOM (Native)
@Component({
selector: 'app-counter',
templateUrl: './counter.component.html',
styleUrls: ['./counter.component.css'],
encapsulation: ViewEncapsulation.Native
})
export class CounterComponent {
@Input() counter = 0;
constructor() { }
inc() { this.counter++; }
dec() { this.counter--; }
}
src/app/counter/counter.component.ts
55. WEB COMPONENTS EVERYWHERE
ANGULAR ELEMENTS Present the counter value
<div>{{counter}}</div>
<button (click)="dec()">Dec</button>
<button (click)="inc()">Inc</button>
src/app/counter/counter.component.html
56. WEB COMPONENTS EVERYWHERE
ANGULAR ELEMENTS Create the manipulation buttons and connect the to the handlers
<div>{{counter}}</div>
<button (click)="dec()">Dec</button>
<button (click)="inc()">Inc</button>
src/app/counter/counter.component.html
68. WEB COMPONENTS EVERYWHERE
IVY - THE NEW RENDERING ENGINE FOR ANGULAR
⸠Incremental builds and uses monomorphic data structures internally (Fast)
⸠Tree Shaking (EfďŹcient)
https://github.com/angular/angular/blob/master/packages/compiler/design/architecture.md
69. WEB COMPONENTS EVERYWHERE
ANGULAR ELEMENTS Using the Ivy hopefully we will have something like:
@Component({
selector: 'app-counter',
templateUrl: './counter.component.html',
styleUrls: ['./counter.component.css'],
customElement: true
})
export class CounterComponent {
@Input() counter = 0;
constructor() { }
inc() { this.counter++; }
dec() { this.counter--; }
}
src/app/counter/counter.component.ts
71. WEB COMPONENTS EVERYWHERE
BENEFITS OF USING ANGULAR ELEMENTS
⸠All components can be reused across JavaScript applications.
⸠Creating Dynamic Components.
⸠Hybrid Rendering - Server Side Rendering with Custom Elements that donât
wait for the application to bootstrap to start working.
⸠Micro Frontends Architecture - Vertical Application Scaling (Working with
multiple small applications instead of a big monolith.)
https://micro-frontends.org
72. WEB COMPONENTS EVERYWHERE
ADDITIONAL RESOURCES
⸠https://custom-elements-everywhere.com - This project runs a suite of tests
against each framework to identify interoperability issues, and highlight
potential ďŹxes already implemented in other frameworks.