0% found this document useful (0 votes)
56 views103 pages

Web Components Guide for Developers

The document is a book titled 'Web Component Essentials' by Cory Rylan, focusing on creating reusable user interfaces using Web Components. It includes various chapters covering topics such as component lifecycle, styles, architecture, and integration with frameworks like Angular, VueJS, and React. The book provides full working code examples and is available for purchase on Leanpub.

Uploaded by

najej32377
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
56 views103 pages

Web Components Guide for Developers

The document is a book titled 'Web Component Essentials' by Cory Rylan, focusing on creating reusable user interfaces using Web Components. It includes various chapters covering topics such as component lifecycle, styles, architecture, and integration with frameworks like Angular, VueJS, and React. The book provides full working code examples and is available for purchase on Leanpub.

Uploaded by

najej32377
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd

Web Component Essentials

An introduction to creating reusable user interfaces with


Web Components. Full working code examples included!

Cory Rylan
This book is for sale at [Link]

This version was published on 2018-10-28

This is a Leanpub book. Leanpub empowers authors and publishers with the Lean Publishing
process. Lean Publishing is the act of publishing an in-progress ebook using lightweight tools and
many iterations to get reader feedback, pivot until you have the right book and build traction once
you do.

© 2018 Cory Rylan


Contents

Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
About the Author . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
What is this Book About? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
Technical Prerequisites . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1

Chapter 1 - The Component . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3


History of the Component . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
What is a Web Component? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
Custom Elements . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4

Chapter 2 - Templates . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
Content Slot API . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
Named Slots . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
Use Case - Dropdown Component . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12

Chapter 3 - Component Communication . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15


Component Properties . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
Component Events . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
Use Case - Dropdown Component . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19
Component Custom Attributes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23

Chapter 4 - Component Lifecycle . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27


JavaScript Class Constructor and Connected Callback . . . . . . . . . . . . . . . . . . . . . . 27
Disconnected Callback . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
Attribute Changed Callback . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
Adopted Callback . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29

Chapter 5 - Component Styles and Themes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30


Global Styles . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
CSS Encapsulation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31
CSS Custom Properties . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34
Dynamic CSS Custom Properties . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36
Theming Web Components . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39

Chapter 6 - Component Hierarchy and Architecture . . . . . . . . . . . . . . . . . . . . . . . . 42


CONTENTS

Component Data Flow . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43


Character List . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45
Character Detail . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48

Chapter 7 - Production Ready(ish) Web Components . . . . . . . . . . . . . . . . . . . . . . . . 51


Publishing with NPM . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51
Browser Support . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54
Polyfills . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55
Installing Webpack and Babel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57
Webpack . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59

Chapter 8 - Using Web Components in Angular and VueJS . . . . . . . . . . . . . . . . . . . . 61


Angular . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61
VueJS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 64

Chapter 9 - Using Web Components in React . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 68


React Compatibility . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 68
Create React App . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 68
Properties and Events . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 69
Prop Updates . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 70

Chapter 10 - lit-html templates . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 73


lit-html and template literal strings . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 73
Templates and Event Listeners . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 74
Properties and Decorators . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 75
Custom Events . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 77
Binding to other Web Components with lit-html . . . . . . . . . . . . . . . . . . . . . . . . . . 78

Chapter 11 - Stencil JS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 81
The Stencil CLI . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 82
Decorators . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 83
JSX Templates . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 84
JSX Component Bindings . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 85
Building your Stencil Components . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 86

Chapter 12 - Building a Todo App with lit-html . . . . . . . . . . . . . . . . . . . . . . . . . . . 87


Implementation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 88
Todos Data Service . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 89
Todos List . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 90
Todo Item . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 94

Chapter 13 - Conclusion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 99
Introduction
About the Author
Cory Rylan is a Front End Web Developer and Google Developer Expert for Angular and Web
technologies. He specializes is high performance Progressive Web Apps and enterprise level web
app development. He currently is a full time developer in the US midwest as well as a part time
contractor. He speaks at conferences, meet-ups and helps teach Angular Bootcamp a three day in
person class. When not speaking and teaching he is writing all about Angular and Web Components
on his blog at [Link]¹. You can also follow him on Twitter @coryrylan²;

What is this Book About?


This books goal is to provide a introduction and practical guide of how to create and use Web
Components. By the end of this book readers can feel confident in creating a reusable UI component
and share it across web technologies and projects.

Technical Prerequisites
The code examples accompanied with this book use the latest in Web technologies. Currently to
get started with as little tooling as possible these examples will work only work in Chrome and
Firefox nightly. In later chapters we will cover how to build Web Components and get cross browser
support.
This book covers beginner to intermediate web specific technologies in HTML, CSS and JavaScript.
Some of the topics used in this book are listed below.

• HTML
– HTML5 tags
– HTML inputs and forms
– Familiar with web accessibility
• CSS
– Familiar with responsive design concepts
– Familiar with some layout techniques such as Flexbox or CSS Grid
¹[Link]
²[Link]
Introduction 2

– Media Queries
– Familiar with CSS pre-processors such as Less or Sass
• JavaScript
– Familiar with ES2015 (ES6) syntax (classes, template strings, modules)
– Familiar with any type of front end build system (grunt, gulp, webpack, ect)
– Basic use of NPM (node package manager)
– Beneficial but not required to be familiar with a JavaScript front end framework such as
Angular, React, Ember, or VueJS
Chapter 1 - The Component
User Interfaces on the web seems to be a problem with a million possible solutions. Every day there is
a new JavaScript framework or library to help build your UIs. How many programming development
years have been used rewriting a date-picker component to work in the latest framework? In the
world of web development, it is not uncommon for our UI widgets and components to be rewritten
across many different flavors of JavaScript frameworks for compatibility.
With the fast pace of the ever-evolving JavaScript and Web ecosystem, it can feel daunting to try and
share code between websites and projects. We have to learn different frameworks, browser support,
and ever-changing web APIs. Versioning, performance, accessibility, and more are all things we have
to think about when building our UI components. It’s becoming more critical that we have stable
and portable components that work everywhere. We have come a long way with the Web, but how
did we get to the idea of Web Components and what are they?

History of the Component


The idea of the component for Web development has been around for a long time. Maybe not quite
the term “component”, but widget or plugin if we think back to the good old days of JQuery and
JQuery UI. We could quickly add interactive widgets to our websites. I remember how excited my
fellow developers and I were when we added that first little bit of JQuery to our Web pages and
suddenly a new world of possibilities was open to us. Then we had libraries and frameworks like
AngularJS, Backbone, and Knockout. Each one of these technologies tried to solve certain parts of
UI development. The web since then has moved on and evolved. We have come so far, but have a
ways to go.
Fast forward to today, we have frameworks and libraries such as React, Angular, Ember, Vue,
and Polymer, which all embrace the idea of the component. Angular was one of the first major
frameworks to have something component-like. Angular’s Directive API used custom element tags.
React shook up Web development through its simple component model. Current JavaScript libraries
and frameworks have their own definition of what a component is. There is no common API, but a
lot of agreed overlap of what a component should be.
We still have this issue of components being built in framework silos. That slick React datepicker
you found? No, sorry you can’t use that in Angular. This repetition happens more often than we
want to admit, but it is a problem we can solve.
Chapter 1 - The Component 4

What is a Web Component?


What is a Web Component from a high-level design perspective? A component is a piece of reusable
user interface. When we think of components things like date-pickers, dropdowns, and modals are
the first that come to mind. Most modern frameworks such as Angular, React, and Vue have all
embraced the idea of the component, but all still go about it slightly different. The Web Component
API is here to help solve this fragmentation and give you true reusability across multiple web
applications regardless of your chosen framework.
The Web Component API is a collection of different APIs that when combined make a powerful tool-
set to create highly reusable UI components. In this book, we will cover each of these core APIs and
then combine them to make some reusable components for our applications. After an introduction,
we will then learn about the available Web Component authoring tools that make it easy to author
and publish our web components. Let’s get started!

Custom Elements
First, we need to start with the basics, HTML. No matter how simple or complex, from static blog
to single page application, HTML is our starting point. We use many built-in components already
when writing HTML. Some of these built-in components have been around for quite a while. HTML
elements like text inputs, dropdowns, and even native date-pickers all are components or reusable
pieces of UI. While the web has provided some of these primitive components, most real-world
applications need some customization or more robust features that these primitives lack.
The starting foundation of Web Components is the Custom Elements API. The Custom Elements
API allows us to define our own custom HTML tags and attach behavior to them. Let’s define our
first custom element. In a single JavaScript file, we are going to add the following:

1 class XComponent extends HTMLElement {


2 constructor() {
3 super();
4 }
5
6 connectedCallback() {
7 [Link] = 'Hello World from a Web Component!';
8 }
9 }
10
11 [Link]('x-component', XComponent);

As you can see in our [Link] file, we are using some of the newer ES2015 JavaScript features such
as Classes. To create our custom element, we extend the HTMLElement base class. When extending a
Chapter 1 - The Component 5

Class in JavaScript, we must call super() in the constructor. The constructor is called whenever a
new instance of our component is created. Every time we use the x-component tag in our HTML, we
will have a new instance created. The constructor is where we will initialize any component state
or initialization logic.
Our component class XComponent has a single method called connectedCallback(). The connectedCallback()
is a particular lifecycle method that is invoked when our component is created and added to
the DOM. Here is where we do most of our initialization DOM logic for our component. The
connectedCallback() method is where most of the heavy lifting initialization logic should occur;
things like data fetching or initial render logic.
In the connectedCallback() method we can set the innerHTML of our component. Because we are
extending the HTMLElement we get all the common properties we get with any standard DOM
element.
Lastly, we need to register our component to the DOM (Document Object Model).

1 [Link]('x-component', XComponent);

We pass two parameters to the custom elements API. The first is the tag name we will use in the
DOM and the second is a reference to our component class definition. In our example, we are going to
call our component x-component. Notice we have a dash in our tag name. With the custom elements
API, it is necessary to have at least one dash in the name. The dash convention protects us from
defining a component that may come to a later version of HTML as native HTML elements have no
dashes.
Now that we have our component defined let’s use it in our HTML.

1 <!DOCTYPE HTML>
2
3 <html>
4 <head>
5 <title>Web Components Rock!</title>
6 </head>
7
8 <body>
9 <x-component></x-component>
10
11 <script type="module" src="./[Link]"></script>
12 </body>
13 </html>

Notice we have the component in our [Link] template as well as a script tag. Take note that the
script tag to our [Link] also has type="module". In this book, we will be using the latest browser
features including ES2015 Modules. Later in the book, we will cover techniques to be able to use
Chapter 1 - The Component 6

these features with cross-browser support. If we now check the browser, we will see our component
render.

Our First Web Component

Congratulations you have created your first Web Component! Our component doesn’t do anything
useful just yet. We need to learn a few more APIs before we can start building something a bit
more interesting. Our next chapter we will develop a dropdown component and learn some of the
template APIs.
Chapter 2 - Templates
In this chapter we are going to cover the templating features provided by Web Component APIs and
build a simple dropdown component using these APIs. The template API provides many low level
features needed to build UI components. The template API does not however support templating
language features we are accustom to in many JavaScript frameworks. For example use cases such
as loops and conditionals are not built into the Template API.
Many of the Web Component APIs are specifically designed to be low level to allow more powerful
opinionated tools to be built on top of them. We will see in later chapters these helpful tools for
authoring Web Components.
The Template API allows us to create neutral HTML templates in our components. In the past tem-
plating languages would use script tags with alternate types such as <script type="templating-lib"></script>.
Script tags with invalid types ensured that the encapsulated HTML in the template would not render
until the JavaScript had time to execute.
Now with the <template> tag the browser ignores any code within those tags. Let’s try it out. We
are going to add the following to our HTML file.

1 <!DOCTYPE HTML>
2
3 <html>
4 <head>
5 <title>Web Component Essentials</title>
6 </head>
7
8 <body>
9 <template>
10 <script>
11 <h1>Not going to render!</h1>
12 alert('not going to happen!');
13 </script>
14 </template>
15 <script type="module" src="./[Link]"></script>
16 </body>
17
18 </html>

If we open the HTML file in the browser, we can see the h1 heading doesn’t render, and we don’t get
an alert from the inner script tag. With this, we can clone the template manipulate it with JavaScript
then make updates to the DOM.
Chapter 2 - Templates 8

If we are using ES2015/JavaScript modules for our components why cover the template tag at all?
Template tags are still useful for performance in creating templates in our components. Let’s take a
look at the code below.

1 const template = [Link]('template');


2 [Link] = `
3 <p>Hello World from Template</p>
4 `;
5
6 class XComponent extends HTMLElement {
7 constructor() {
8 super();
9 [Link] = [Link]({ mode: 'open' });
10 [Link]([Link](true));
11 }
12 }
13
14 [Link]('x-component', XComponent);

With this technique, we can create a single instance of our template. Now every time we use
x-component we reduce the parsing costs of creating the template. This technique gives us a
performance gain when instantiating our components. With our component, we see our first use
of a new API called the Shadow DOM. For now, we use this to get a reference to our component
element to attach our template. In later chapters, we will see the many advantages of using this new
Shadow DOM API.

Content Slot API


There is one more template related API to cover before moving on. The Content Slot API is a useful
API that allows us to pass content into a component template declaratively. For example, let’s look
at this use of our <x-component>.

1 <x-component>
2 Hello World!
3 </x-component>

If we look at the rendered output, we will see the message “Hello World!” inside the <x-component>
tags.
Chapter 2 - Templates 9

Rendered Slot Component

Let’s take a look at the <x-component> code.

1 const template = [Link]('template');


2 [Link] = `
3 <p>
4 <slot></slot>
5 </p>
6 `;
7
8 class XComponent extends HTMLElement {
9 constructor() {
10 super();
11 [Link] = [Link]({ mode: 'open' });
12 [Link]([Link](true));
13 }
14 }
15
16 [Link]('x-component', XComponent);

In the template we have a paragraph element and within the paragraph a <slot> element. The slot
element allows us to declare where the content passed into our component should render. If we look
at the output of <x-component> we will see that “Hello World!” is rendered between the component
tags but as well as our paragraph element. The Slot API is only available if your template is initialized
with a shadow DOM instance like we see in our constructor().
Chapter 2 - Templates 10

Named Slots
The template slot API also supports multiple Slot insertion points in components. Multiple slots are
useful when we want a structured component with lots of dynamic content. For example, we have
a detail card component that displays an article or some blog post content.
In this detail card we want to display a title and snippet of text. With named slots we can define
multiple points where the dynamic content can be inserted. One can be inserted for the heading of
the card and another for the content. Let’s go ahead and take a look at a updated template to get a
better understanding.

1 <style>
2 .card {
3 padding: 12px;
4 border: 1px solid #ccc;
5 }
6
7 .title {
8 border-bottom: 1px solid #ccc;
9 margin-bottom: 12px;
10 }
11 </style>
12 <div class="card">
13 <div class="title">
14 <slot name="title"></slot>
15 </div>
16 <slot></slot>
17 </div>

In the template of our new component, x-detail-card, we have some styles associated to our
template. Notice we have two <slot> elements. The first has a name attribute with the value of
title. Slots allow you to give specific names so when you use the component you can define where
each piece of content should be rendered. If you don’t define a name then the content will be rendered
in the default unnamed <slot>. Let’s take a look at what it looks like to use our x-detail-card.
Chapter 2 - Templates 11

1 <!DOCTYPE HTML>
2
3 <html>
4
5 <head>
6 <title>Web Component Essentials</title>
7 </head>
8
9 <body>
10 <x-detail-card>
11 <span slot="title">Hello!</span>
12 from multi slot component
13 </x-detail-card>
14
15 <script type="module" src="./[Link]"></script>
16 </body>
17
18 </html>

When we use the x-detail-card our span has a slot attribute that defines which slot it should
render in. For ours its the title slot. This will insert the span into the title slot which means
we will get any styles associated with it as well. Our content from multi slot component will be
rendered in the default slot as we did not define a named slot. Here is the rendered output of using
the x-detail-card:

Rendered Multi Named Slot Component

We will see in our next section how the slot API has many practical uses.
Chapter 2 - Templates 12

Use Case - Dropdown Component


Now are going to take what we learned about the custom elements API and template tag API to build
a simple dropdown style component. First, let’s take a look at the rendered output of the dropdown
component.

Dropdown component

To create this component, we will need to define our custom element class.

1 const template = [Link]('template');


2 [Link] = `
3 <button>Dropdown</button>
4 <div>
5 <slot></slot>
6 </div>
7 `;
8
9 class XDropdown extends HTMLElement {
10 constructor() {
11 super();
12 [Link] = [Link]({ mode: 'open' });
13 [Link]([Link](true));
14 }
Chapter 2 - Templates 13

15 }
16
17 [Link]('x-dropdown', XDropdown);

Our dropdown component template has a single button to toggle our content. We also use the
Content Slot API we saw earlier to take in content easily for our component template to render.
Next, we need to add some DOM references to our component to access the button and content.

1 class XDropdown extends HTMLElement {


2 constructor() {
3 super();
4 [Link] = [Link]({ mode: 'open' });
5 [Link]([Link](true));
6 }
7
8 connectedCallback() {
9 // Root also gives us access to the inner template of our component
10 [Link] = [Link]('button');
11 [Link]('click', () => [Link]());
12
13 // Set the initial dropdown content to be hidden
14 [Link] = [Link]('div');
15 [Link] = 'none';
16 }
17
18 toggle() {
19 const show = [Link] === 'block'
20 [Link] = show ? 'none' : 'block';
21 }
22 }

In our constructor, we can query the template with our root element property. We create references
to the button and content. With the content DOM references set, we can set the style property to
display none to hide the slot content by default. We next create a click event listener on our button.
This event listener will call the toggle() method whenever the button is clicked.
A important note, we are having to set up events and set properties of our template manually. In
most modern JavaScript templating libraries/frameworks it abstracts this boilerplate code away. In
a later chapter, we will see how we can add custom templating systems to make it easier to handle
events and set custom properties. Now in our [Link] we can use our new dropdown component.
Chapter 2 - Templates 14

1 <!DOCTYPE HTML>
2
3 <html>
4 <head>
5 <title>Web Component Essentials</title>
6 </head>
7 <body>
8 <x-dropdown>
9 Hello World!
10 </x-dropdown>
11
12 <script type="module" src="./[Link]"></script>
13 </body>
14 </html>

Congratulations you now have a functioning dropdown component! But wait, what if we want to
change the text value of the button? What if we need to know when the dropdown has been opened
or closed? How do we accomplish this? In our next chapter we will dive into component communi-
cation with properties and events to extend the functionality our new dropdown component!
Chapter 3 - Component
Communication
In our previous chapter, we successfully built our dropdown component. Now we need the ability
to customize some aspects of it. Ideally we want to be able to change the button text to whatever
makes sense for our UI. How do we accomplish this? With Web Components, we typically pass data
into components by setting properties or attributes. In this chapter, we will cover both techniques
and the best practices when passing data to components.

Component Properties
First, we are going to cover how to set the inner text of our dropdown button using custom properties.
With properties we can pass any JavaScript data type to our components, this includes things like
Objects and Arrays. Let’s go ahead and dive into our component and see how we define these
properties.

1 const template = [Link]('template');


2 [Link] = `
3 <p></p>
4 `;
5
6 class XComponent extends HTMLElement {
7 // ES2015 classes support Getters and Setters
8 set name(value) {
9 this._name = value;
10 [Link] = this._name;
11 }
12
13 get name() {
14 return this._name;
15 }
16
17 constructor() {
18 super();
19 [Link] = [Link]({ mode: 'open' });
20 [Link]([Link](true));
21 [Link] = [Link]('p');
Chapter 3 - Component Communication 16

22 }
23 }
24
25 [Link]('x-component', XComponent);

ES2015 Classes support Getters and Setters. With Getters and Setters, we can define properties on
our component that when set we can execute some logic on our component. In this example, we have
a pubic Getter and Setter for the name property. Our getter returns the value of _name which is our
private property to hold the value that will be rendered. JavaScript, unfortunately, does not support
private properties yet. Because of the lack of a formal private keyword, we follow the standard
JavaScript convention of prefixing private properties with an underscore.
Our Setter for name sets the private _name as well as sets the inner text of the nameElement reference
we created in our constructor(). Now anyone using our component can set the name property of
our component and the template will reflect those changes.

1 import './[Link]';
2
3 const component = [Link]('x-component');
4 [Link] = 'Hello World from Web Component property!';

Setting custom properties on components is the primary and recommended way to pass data into
components. By setting properties we can pass any data type to the component. Many frameworks
follow this pattern as well as providing an easy way to bind data to components in declaratively
in HTML templates. Some Web Component libraries also simplify declaring properties on your
component by automatically creating your Getters and Setters or some advanced form of data
change detection. In later chapters, we will take a look at these useful tools in more detail.
Now we know how to set or pass data to a component what about the use case of when we want a
component to communicate or notify us of a change or user interaction?

Component Events
Component Events provide a mechanism to notify us of a change of some kind. For example, when
we use the HTML <button> element, we can listen to the click event to be notified of when the user
clicks our button. We can set up similar custom event on our own custom component. Let’s look at
an example below.
Chapter 3 - Component Communication 17

1 const template = [Link]('template');


2 [Link] = `
3 <button>click me!</button>
4 `;
5
6 class XComponent extends HTMLElement {
7 constructor() {
8 super();
9 [Link] = [Link]({ mode: 'open' });
10 [Link]([Link](true));
11
12 [Link]
13 .querySelector('button')
14 .addEventListener('click', () => [Link]('button clicked'));
15 }
16 }
17
18 [Link]('x-component', XComponent);

In this example, we have a simple component with a single button in the template. In our
constructor() we select the button from our template and add an event listener for the click event.

1 [Link]
2 .querySelector('button')
3 .addEventListener('click', () => [Link]('button clicked'));

Now we can log when the button is clicked, but this is only part of the solution. We need to create
a custom event for our XComponent, so anyone using our XComponent can get notified of when the
user clicks the button.

1 const template = [Link]('template');


2 [Link] = `
3 <button>click me!</button>
4 `;
5
6 class XComponent extends HTMLElement {
7 constructor() {
8 super();
9 [Link] = [Link]({ mode: 'open' });
10 [Link]([Link](true));
11
12 [Link]
Chapter 3 - Component Communication 18

13 .querySelector('button')
14 .addEventListener('click', () => [Link]());
15 }
16
17 handleClick() {
18 [Link](
19 new CustomEvent('customClick', { detail: [Link]() })
20 );
21 }
22 }
23
24 [Link]('x-component', XComponent);

In our example above we can see we added a new method handleClick. This method will get called
whenever the click event of the button occurs. In handleClick we trigger a new custom event.

1 handleClick() {
2 [Link](new CustomEvent('customClick', { detail: [Link]() }));
3 }

When we dispatch a new custom event, we provide two parameters. First is the name we want for
our custom event. In this example, we have customClick. Now when anyone uses our component,
they can listen for the customClick event.
The second parameter is a configuration object for our event. In our configuration, we have a single
property called detail. The detail property is where we can attach any value to our event to pass
to any listener of the event. Our example we are just going to pass back a random number. Let’s take
a look at some code that listens to our new custom event.

1 import './[Link]';
2
3 const component = [Link]('x-component');
4 [Link]('customClick', e => [Link](e));

You can see we can select the DOM instance of our component and create an event listener for our
customClick event just like any other DOM event. If we look at the value of our event, we get a lot
more back than only the event value.
Chapter 3 - Component Communication 19

Custom Event Output

In the custom event, we get all the useful DOM details about our component as well as our detail
property with the custom value we passed back.
In a future chapter we will see some examples of how Web Component libraries can help easily
declare and listen to component events. Now that we have learned the API for custom properties
and events let’s take a look at how we can improve our dropdown component using these APIs.

Use Case - Dropdown Component


Going back to our dropdown component, we want to add some additional functionality. First, we
want to be able to change the text of the dropdown button. Second, we want to be able to create a
custom event listener, so we know when the user has opened or closed our dropdown. Let’s start
with the custom button text.

Dropdown Component - Custom Properties


We want to set the button text to a custom string value. To accomplish this, we need to pass data
down to the dropdown. By using custom properties we learned earlier we can achieve this.
Chapter 3 - Component Communication 20

1 const template = [Link]('template');


2 [Link] = `
3 <button></button>
4 <div>
5 <slot></slot>
6 </div>
7 `;
8
9 class XDropdown extends HTMLElement {
10 get title() {
11 return this._title;
12 }
13
14 set title(value) {
15 this._title = value;
16 [Link] = this._title;
17 }
18
19 constructor() {
20 super();
21 this._title = 'dropdown';
22 [Link] = false;
23 [Link] = [Link]({ mode: 'open' });
24 [Link]([Link](true));
25
26 [Link] = [Link]('button');
27 [Link] = [Link];
28 [Link]('click', () => [Link]());
29
30 [Link] = [Link]('div');
31 [Link] = 'none';
32 }
33
34 toggle() {
35 [Link] = ![Link];
36 [Link] = [Link] ? 'block' : 'none';
37 }
38 }
39
40 [Link]('x-dropdown', XDropdown);

On our dropdown, we create the title property getter and setter as well as a private _title property.
Using the getter and setters, we can change the button innerText when the title property is set.
Chapter 3 - Component Communication 21

1 import './[Link]';
2
3 const dropdown = [Link]('x-dropdown');
4
5 [Link] = 'Custom Title';
6 // wait three seconds then update the property
7 setTimeout(() => ([Link] = 'New Custom Title'), 3000);

Just like any DOM element once we have defined a custom property, we can pass data to it. In
this example, we set the dropdown button to Custom Title. The dropdown is also updated if this
property is changed later. In this snippet, we create a setTimeout that will change the button text to
New Custom Title three seconds later.

We have successfully passed data to our dropdown component, and the component updates its
template. Next, we want to get notified when a user has clicked on our dropdown component.

Dropdown Component - Custom Events


To get notified of when the user has clicked our dropdown component we need to create a custom
event similar to our example we learned earlier. We will need to have a click event for the button to
trigger the custom event to be dispatched. Let’s take a look at our dropdown code.

1 const template = [Link]('template');


2 [Link] = `
3 <button></button>
4 <div>
5 <slot></slot>
6 </div>
7 `;
8
9 class XDropdown extends HTMLElement {
10 get title() {
11 return this._title;
12 }
13
14 set title(value) {
15 this._title = value;
16 [Link] = this._title;
17 }
18
19 constructor() {
20 super();
21 this._title = 'dropdown';
Chapter 3 - Component Communication 22

22 [Link] = false;
23
24 [Link] = [Link]({ mode: 'open' });
25 [Link]([Link](true));
26
27 [Link] = [Link]('button');
28 [Link] = [Link];
29 [Link]('click', () => [Link]());
30
31 [Link] = [Link]('div');
32 [Link] = 'none';
33 }
34
35 toggle() {
36 [Link] = ![Link];
37 [Link] = [Link] ? 'block' : 'none';
38
39 // trigger our custom event 'show' for others to listen to
40 [Link](new CustomEvent('show', { detail: [Link] }));
41 }
42 }
43
44 [Link]('x-dropdown', XDropdown);

In our dropdown component, we added our custom event in the toggle() method. When the user
clicks the dropdown button, our dropdown will now dispatch a custom event show which will return
a boolean detail value to tell us if the dropdown is visible or not.

1 import './[Link]';
2
3 const dropdown = [Link]('x-dropdown');
4 [Link]('show', e => [Link](e));

As you can see, we can now listen to our custom show event and get notified when the user has
clicked the dropdown as well as know if the dropdown has opened or closed.
The use of custom properties and events is our primary communication mechanism for components.
We can pass data down to components via properties and listen to data being passed back via events.
Chapter 3 - Component Communication 23

Component Communication

Before we move onto the next chapter, there is one more topic we need to cover when interacting
with our components. So far we have been communicating or passing data down to our components
via JavaScript properties, but what about using DOM attributes instead? In our next section, we will
talk about the pros and cons of using DOM attributes for component communication.

Component Custom Attributes


It is common when using web components to set attributes in the HTML to pass information to the
component. Let’s look at this next code example.

1 <x-component message="hello world"></x-component>

As you can see we are setting a custom HTML attribute on our x-component. We can read the
values of the attributes on our component. Attributes can be a convenient way to pass information
to a component without the need of additional JavaScript. The downside to HTML Attributes is
the Attribute is always treated as a string so you cannot use other data types such as numbers or
Chapter 3 - Component Communication 24

objects without doing additional parsing of the string value. Let’s take a look at what it takes to read
attribute values on a custom element.

1 const template = [Link]('template');


2 [Link] = `
3 <p></p>
4 `;
5
6 class XComponent extends HTMLElement {
7 static get observedAttributes() {
8 return ['name'];
9 }
10
11 set name(value) {
12 this._name = value;
13 [Link] = this._name;
14 }
15
16 get name() {
17 return this._name;
18 }
19
20 constructor() {
21 super();
22 [Link] = [Link]({ mode: 'open' });
23 [Link]([Link](true));
24 [Link] = [Link]('p');
25 }
26
27 attributeChangedCallback(attrName, oldValue, newValue) {
28 if (attrName === 'name') {
29 [Link] = newValue;
30 }
31 }
32 }
33
34 [Link]('x-component', XComponent);

The first part of the component is the observedAttributes() method.


Chapter 3 - Component Communication 25

1 static get observedAttributes() {


2 return ['name'];
3 }

The static method observedAttributes expects a list of named attributes that the DOM should watch
for changes. This is a required performance optimization, so you receive updates about which at-
tributes have changed. The second part of the custom Attributes API is the attributeChangedCallback()
method.

1 attributeChangedCallback(attrName, oldValue, newValue) {


2 if (attrName === 'name') {
3 [Link] = newValue;
4 }
5 }

This callback method is called whenever one of our listed attribute values has been updated. The
method takes three parameters. The first is the name of the attribute that changed so we can
determine if and how to update our component. The second and third attribute gives you the
previous and new value of the attribute value so you can efficiently update your component.
Let’s take what we learned with custom attributes and add support to our dropdown component to
change the name via an Attribute.

1 const template = [Link]('template');


2 [Link] = `
3 <button></button>
4 <div>
5 <slot></slot>
6 </div>
7 `;
8
9 class XDropdown extends HTMLElement {
10 static get observedAttributes() {
11 return ['title'];
12 }
13
14 get title() {
15 return this._title;
16 }
17
18 set title(value) {
19 this._title = value;
20 [Link] = this._title;
Chapter 3 - Component Communication 26

21 }
22
23 constructor() {
24 super();
25 this._title = 'dropdown';
26 [Link] = false;
27
28 [Link] = [Link]({ mode: 'open' });
29 [Link]([Link](true));
30
31 [Link] = [Link]('button');
32 [Link] = [Link];
33 [Link]('click', () => [Link]());
34
35 [Link] = [Link]('div');
36 [Link] = 'none';
37 }
38
39 attributeChangedCallback(attrName, oldValue, newValue) {
40 if (attrName === 'title' && oldValue !== newValue) {
41 [Link] = newValue;
42 }
43 }
44
45 toggle() {
46 [Link] = ![Link];
47 [Link] = [Link] ? 'block' : 'none';
48 [Link](new CustomEvent('show', { detail: [Link] }));
49 }
50 }
51
52 [Link]('x-dropdown', XDropdown);

Component input Properties and output Events are our primary communication mechanism to
communicate with our components. In later chapters we will also cover how we can use these
same mechanisms to have components communicate between each other. In our next chapter we
will cover component styles and themes.
Chapter 4 - Component Lifecycle
This chapter we will cover the Custom Element lifecycle. We have already covered a few lifecycle
methods in previous chapters. Let’s walk through them in order. First the constructor.

JavaScript Class Constructor and Connected Callback


1 class XComponent extends HTMLElement {
2 constructor() {
3 super();
4 }
5
6 connectedCallback() {
7 [Link] = 'hello world';
8 }
9 }
10
11 [Link]('x-component', XComponent);

The constructor is executed whenever an instance of our component is created. However, if we need
to instantiate DOM or initialization logic we will want to run this code in the connectedCallback()
lifecycle hook. The connectedCallback() is executed whenever our component is added to the DOM.
Once added we can start rendering and interacting with the DOM.

Disconnected Callback
Our next lifecycle hook is the disconnectedCallback() method. The disconnectedCallback()
method is called whenever our component is removed from the DOM. Let’s take a look at the
following example:
Chapter 4 - Component Lifecycle 28

Removing and adding a component

In our template, we render a component then add or remove the component based on a checkbox
value.

1 <x-component></x-component>

1 class XComponent extends HTMLElement {


2 disconnectedCallback() {
3 [Link]('disconnectedCallback');
4 }
5 }
6
7 [Link]('x-component', XComponent);

1 import './[Link]';
2
3 const toggle = [Link]('#toggle');
4 const main = [Link]('main');
5
6 [Link]('change', e => {
7 if ([Link]) {
8 [Link]([Link]('x-component'));
9 } else {
10 [Link]([Link]('x-component'))
11 }
12 });

Using disconnectedCallback() is helpful for when we need to do any kind of cleanup work when
our component is removed, for example disconnecting from Web Sockets or event listeners.
Chapter 4 - Component Lifecycle 29

Attribute Changed Callback


Our next lifecycle hook we have covered with our dropdown component. The attributeChangedCallback()
is useful for notifying us whenever an attribute on our element has changed.

1 class XComponent extends HTMLElement {


2 static get observedAttributes() {
3 return ['value'];
4 }
5
6 attributeChangedCallback(attrName, oldVal, newVal) {
7 [Link]('attributeChangedCallback', attrName, newVal);
8 }
9 }
10
11 [Link]('x-component', XComponent);

To get notified we must list the attributes for the element to watch. We then add the attributeChangedCallback()
which pass what attribute changed as well as the previous and next values.

1 <x-component value="hello"></x-component>
2 <!-- updated to the following -->
3 <x-component value="hello world"></x-component>

1 // logged values
2 {
3 attrName: 'value',
4 oldVal: 'hello',
5 newVal: 'hello world'
6 }

Adopted Callback
The last lifecycle hook adoptedCallback() is likely the least commonly used. The adoptedCallback()
is called whenever the element has been moved to a new document. Using adoptedCallback()
most commonly happens when cloning from an element in an iframe and moving it to the parent
document with [Link](). You can read more on the MDN Documentation³.
³[Link]
Chapter 5 - Component Styles and
Themes
In this chapter, we are going to cover how to style Web Components. We will address some of the
pain points of CSS and how the Shadow DOM API can help. Later in the chapter, we will use new
CSS features such as CSS Custom Properties to make our components customizable and theme-able.

Global Styles
One of the big pain points of CSS is that CSS by default is global. For example, let’s look at this
simple CSS rule below.

1 h1 {
2 color: red;
3 }

This simple CSS rule makes all h1 headings red. By default with CSS, this rule will apply to all h1
elements in the document or view. Many times we only want to apply certain rules for certain
components or views. To get around this in the past, we have to come up with clever naming
conventions for CSS classes to avoid global styles from accidentally overriding component specific
styles. For example, if we wanted to apply some CSS to only h1 elements in our article view we
might write something like this:

1 [Link]-heading {
2 color: blue;
3 }

Now when the h1 has the .article-heading class, it will be blue and not apply to all h1 elements.
This works but does not scale well as its easy to have name collisions. What can we do to solve this?
With Web Components, we can use the Shadow DOM API to simplify how we write CSS for our
Web Applications. We have already covered some of the Shadow DOM features like the Slot API for
our dropdown component. The next feature we are going to cover is one of the significant benefits
of using the Shadow DOM, CSS Encapsulation.
Chapter 5 - Component Styles and Themes 31

CSS Encapsulation
CSS Encapsulation has been a long awaited feature of the Web. CSS Encapsulation allows us to write
component specific CSS that will only apply to a particular subset of DOM. To illustrate this let’s go
ahead and jump right into some code.

1 <!DOCTYPE HTML>
2
3 <html>
4 <head>
5 <title>Web Component Essentials</title>
6
7 <style>
8 p {
9 color: blue;
10 }
11 </style>
12 </head>
13 <body>
14 <p>
15 I'm a blue paragraph from the global styles.
16 </p>
17 <x-component></x-component>
18
19 <script type="module" src="./[Link]"></script>
20 </body>
21 </html>

So in our example, we have an [Link] file. In this file, we have declared a single global style.
This style rule sets all p, paragraph tags to be blue. We then also have a single component on the
view, our x-component. Let’s take a look at what it looks like rendered.
Chapter 5 - Component Styles and Themes 32

CSS Encapsulation

Our rendered output we can see a blue paragraph, but we also see a paragraph rendered by our
custom x-component. Notice this component rendered its paragraph red. Let’s go ahead and jump
into the component code and see what is going on here.

1 const template = [Link]('template');


2 [Link] = `
3 <style>
4 p {
5 color: red;
6 }
7 </style>
8 <p>
9 I'm a red paragraph in a web component
10 </p>
11 `;
12
13 class XComponent extends HTMLElement {
14 constructor() {
15 super();
16 [Link] = [Link]({ mode: 'open' });
17 [Link]([Link](true));
18 }
19 }
20
21 [Link]('x-component', XComponent);

Our component is pretty simple, no behavior just a single template with a static paragraph tag
and a style tag. Because we register the template as a shadow element, we automatically get CSS
Encapsulation for free! Notice in the style tag we set the following rule:
Chapter 5 - Component Styles and Themes 33

1 p {
2 color: red;
3 }

Typically that would cause all p elements to be red. In our component, this style is encapsulated to
only apply to this component’s template. Our global styles are left unchanged. Let’s take a look at
the rendered DOM output to see how the browser treats our component.

CSS Encapsulation Shadow DOM Output

In our DOM output, we can see that the browser created a Shadow Root for our component. You
can kind of think of this as a subset or isolated subtree of DOM. This mechanism is what provides
our CSS Encapsulation behavior. In later chapters when we dive into Component authoring tools,
we will cover tooling that will allow you to easily write CSS in stand-alone CSS files or even Sass
or Less.
The CSS encapsulation mechanism works both ways; component styles don’t leak out to the global
scope, and global styles don’t override component styles. CSS encapsulation solves so many of the
headaches we can have with CSS. But there are a few scenarios still left for questioning. What if I
do want to override the component styles? Maybe I want a custom theme? These are all questions
we will answer in our next sections, CSS Custom Properties, and Component Themes.
Chapter 5 - Component Styles and Themes 34

CSS Custom Properties


In our previous section, we learned about CSS Encapsulation and the problems it solves for us. This
section we will cover some new CSS features that when combined with Shadow DOM gives us
powerful theming capabilities with our components.
First, we are going to cover a new CSS API, CSS Custom Properties. CSS Custom Properties solve a
few different issues for us. First, let’s take a look at a simple example this API.

1 <!DOCTYPE HTML>
2
3 <html>
4
5 <head>
6 <title>Web Component Essentials</title>
7 <style>
8 :root {
9 --primary-color: blue;
10 --heading-size: 18px;
11 }
12
13 h2 {
14 color: var(--primary-color);
15 font-size: var(--heading-size);
16 }
17
18 p {
19 color: var(--primary-color);
20 }
21 </style>
22 </head>
23 <body>
24 <h2>Heading styled with a CSS Custom Property</h2>
25 <p>
26 Paragraph styled with a CSS Custom Property
27 </p>
28 </body>
29 </html>

In our HTML file, we have a single style tag as well as an h2 heading and a p tag. In our styles, we
have our first glimpse of the CSS Custom Properties feature.
Chapter 5 - Component Styles and Themes 35

1 :root {
2 --primary-color: blue;
3 --heading-size: 18px;
4 }
5
6 h2 {
7 color: var(--primary-color);
8 font-size: var(--heading-size);
9 }
10
11 p {
12 color: var(--primary-color);
13 }

The :root selector will select the highest root DOM element. In our use case, this is the global
Document. In the :root selector we can define our first CSS Custom Properties. In this example, we
have two custom properties, --primary-color and --heading-size. Custom properties can be any
name but must start with two dashes. To use these variables in our CSS, we can jump down to our
h2 and p selectors.

1 h2 {
2 color: var(--primary-color);
3 font-size: var(--heading-size);
4 }

To assign a value to a property, we use the var() keyword passing in the variable we want to assign.
These variable are like any other language variable in that we can use them in multiple places. Note
in our p tag selector we can reuse the --primary-color variable.

1 p {
2 color: var(--primary-color);
3 }

CSS Custom Properties are great, we have built in variables now. We once had to rely on Sass and
Less to get this kind of behavior. CSS Custom Properties take it one step further than Sass or Less.
Sass and Less take your variables and compiles it to static CSS. Example:
Chapter 5 - Component Styles and Themes 36

1 $primary-color: blue;
2 $size: 18px;
3
4 h2 {
5 color: $primary-color;
6 font-size: $size;
7 }
8
9 p {
10 color: $primary-color;
11 }

This Sass code above compiles to:

1 h2 {
2 color: blue;
3 font-size: 18px;
4 }
5
6 p {
7 color: blue;
8 }

Notice how the CSS generated by Sass is not truly dynamic. There is no way at runtime in the
browser to change the variable as the Sass variable to converted static string values. CSS Custom
Properties are genuinely dynamic and can achieve this behavior. Let’s take a look.

Dynamic CSS Custom Properties


In our previous example, we had a heading and paragraph that had its styles set with CSS Custom
Properties. One of the significant benefits of CSS Custom Properties is we can dynamically change
the variables at runtime with JavaScript. Let’s take a look at an example below.
Chapter 5 - Component Styles and Themes 37

1 <!DOCTYPE HTML>
2
3 <html>
4
5 <head>
6 <title>Web Component Essentials</title>
7 <style>
8 :root {
9 --primary-color: blue;
10 --heading-size: 18px;
11 }
12
13 h2 {
14 color: var(--primary-color);
15 font-size: var(--heading-size);
16 }
17
18 p {
19 color: var(--primary-color);
20 }
21 </style>
22 </head>
23
24 <body>
25 <h2>Heading styled with a CSS Custom Property</h2>
26 <p>
27 Paragraph styled with a CSS Custom Property
28 </p>
29
30 <button>Toggle Theme</button>
31
32 <script type="module" src="./[Link]"></script>
33 </body>
34
35 </html>

Our HTML is the same as before with a couple of exceptions. We now have a button and JavaScript
file we will use to toggle the styles whenever we click the button. Let’s take a look at the two different
toggled states.
Chapter 5 - Component Styles and Themes 38

CSS Custom Properties initial state

CSS Custom Properties second state

When the view first renders the color of the text is blue. When we click the button the text then
turns green. Let’s take a look at the JavaScript that makes this possible.

1 document
2 .querySelector('button')
3 .addEventListener('click', () => toggleStyles());
4
5 function toggleStyles() {
6 const styles = getComputedStyle([Link]);
7 // You can get CSS Custom Properties
8 const colorValue = [Link]('--primary-color');
9
10 if (colorValue === 'green') {
11 // You can also set CSS Custom Properties
12 [Link]('--primary-color', 'blue');
Chapter 5 - Component Styles and Themes 39

13 } else {
14 [Link]('--primary-color', 'green');
15 }
16 }

As you can see in our example, we can read and write our custom properties dynamically, allowing
us to make style updates across multiple CSS selectors. Custom Properties makes theming much
more straightforward than previous techniques. Now that we have covered the benefits and uses of
CSS Custom Properties let’s next take a look at how we can use them to style and theme our custom
Web Components.

Theming Web Components


In this next section, we will cover how we can leverage Custom CSS Properties to style our Web
Components. In our example, we have a paragraph that is styled globally as well as a paragraph in
a web component with some default styles. The global styles make the paragraphs blue while the
Web Component styles its paragraphs red. Here is the rendered output.

Theming Custom Web Components

In our example, we want to be able to customize/theme our web component and change the color
to be green instead of red. Let’s start with the component code first.

1 const template = [Link]('template');


2 [Link] = `
3 <style>
4 :host {
5 --color: red;
6 --size: 16px;
7 }
8
9 p {
10 color: var(--color);
11 font-size: var(--size);
Chapter 5 - Component Styles and Themes 40

12 }
13 </style>
14 <p>
15 I'm a web component that can have custom styles thanks to CSS Properties!
16 </p>
17 `;
18
19 class XComponent extends HTMLElement {
20 constructor() {
21 super();
22 [Link] = [Link]({ mode: 'open' });
23 [Link]([Link](true));
24 }
25 }
26
27 [Link]('x-component', XComponent);

Our component has a small static template with a single paragraph tag. In our CSS we have a new
selector, the :host selector. The host selector refers to the host element, for our component that
would be the x-component tag. Using the :host selector we can set Custom CSS Properties on our
component to use.

1 :host {
2 --color: red;
3 --size: 16px;
4 }
5
6 p {
7 color: var(--color);
8 font-size: var(--size);
9 }

On our :host selector we are creating two properties, --color and --size. We will use these to
style our component. In our styles, we see that the p tag is styled using our custom properties.
When creating Custom CSS Properties on a Web Component we can easily set these properties
with additional CSS, making theming and customizing components easier. Let’s take a look a the
[Link] file.
Chapter 5 - Component Styles and Themes 41

1 <!DOCTYPE HTML>
2
3 <html>
4
5 <head>
6 <title>Web Component Essentials</title>
7 <style>
8 p {
9 color: blue;
10 }
11
12 x-component {
13 --color: green;
14 --size: 24px;
15 }
16 </style>
17 </head>
18
19 <body>
20 <p>I am a paragraph.</p>
21 <x-component></x-component>
22
23 <script type="module" src="./[Link]"></script>
24 </body>
25
26 </html>

In our global styles, we can set the custom properties of our x-component with little effort.

1 x-component {
2 --color: green;
3 --size: 24px;
4 }

Theming Custom Web Components

Custom CSS Properties allow consumers of our components to easily style and theme our compo-
nents while still keeping the benefits of CSS encapsulation.
Chapter 6 - Component Hierarchy and
Architecture
In this chapter, we are going to discuss component hierarchy and communication. We have briefly
touched these topics in our previous chapter where we built a dropdown component. We are going
to look at how components can communicate with each other and where the Web Platform falls a
bit short in aiding application architecture.
In our use case, we are going to walk through a list/detail view. A common UI pattern is to have a
list view and when a list item is selected a detail view of that particular item is displayed. We will
walk through how to build this out with a recommended component structure. Here is what our
list/detail view looks like:

List/Detail View with Web Components

In our view, we have a list of Star Wars characters loaded from the open source Star Wars API⁴.
When we click one of the characters in our list we will see a detail view of that character to the right
of the screen.
⁴[Link]
Chapter 6 - Component Hierarchy and Architecture 43

Component Data Flow


We are going to follow some general best practices for component architecture/design. First, we
want to keep the character list and character detail components generic and reusable. This means
the character list and detail components should not fetch or request data but have that data passed
in via custom properties. If we were to draw out the data flow of our application it would look like
the following:

Data flow with Web Components

We fetch data from our root or main application code. Our code then passes that data to our
characters list component via the characters custom property. The characters property keeps the
characters list component generic and reusable as it does not care where the data comes from.
Chapter 6 - Component Hierarchy and Architecture 44

Our character list component also needs to communicate back to the root when the user has selected.
The character list will have a custom event selectedCharacter for our root component to listen to.
In the event, we will pass back which character was selected by the user. This event allows the root
component to take the selected character then and pass it to the character detail component. Like
the list component, the detail component is generic as it takes its data as an input property. The
detail component does not care where the data comes from, give it the data, and it does its one
responsibility to render that data.
This pattern we see above is a typical component pattern used across almost all component libraries
and frameworks; examples include Angular, Vue, and React. The primary idea is to have the majority
of our components to be rendered or pure rendering components. Similar to a pure function they
take in data and render a template as its output. This makes components reusable and typically easier
to unit test. Let’s go ahead and jump into the code for our example.

1 <!DOCTYPE HTML>
2
3 <html>
4
5 <head>
6 <title>Web Component Essentials</title>
7 </head>
8 <body>
9 <main>
10 <h2>Galactic Characters</h2>
11 <x-character-list></x-character-list>
12 <x-character-detail></x-character-detail>
13 </main>
14
15 <script type="module" src="./[Link]"></script>
16 </body>
17 </html>

In our root template or [Link] we have our starting [Link] file and our top-level components,
x-character-list and x-character-detail. Our [Link] file will fetch our API data and pass that
data along to our child list and detail components.
Chapter 6 - Component Hierarchy and Architecture 45

1 import './[Link]';
2 import './[Link]';
3
4 const characterListComponent = [Link]('x-character-list');
5 const characterDetailComponent = [Link]('x-character-detail');
6
7 [Link]('selectCharacter', e => characterDetailCompo\
8 [Link] = [Link]);
9
10 fetch('[Link]
11 .then(res => [Link]())
12 .then(data => {
13 [Link] = [Link];
14 [Link] = [Link][0];
15 });

In our [Link] file we get DOM references to our components. We listen to the custom event
selectCharacter from our characterListComponent. This event will notify us when a user has
selected a character. When the event fires it passes a reference of the character that was selected so
we can pass it along to our characterDetailComponent to render.

Character List
We then fetch our user data from the API we pass the list of characters to the custom input property
characters on the characterListComponent. We also pass an initial character to the character input
property on the characterDetailComponent. Let’s take a look at the characterListComponent next
and then break it down, line by line.

1 const template = [Link]('template');


2 [Link] = `
3 <style>
4 button {
5 display: block;
6 padding: 12px;
7 width: 100%;
8 }
9 </style>
10 <section></section>
11 `;
12
13 class XCharacterList extends HTMLElement {
14 get characters() {
Chapter 6 - Component Hierarchy and Architecture 46

15 return this._characters;
16 }
17
18 set characters(value) {
19 this._characters = value;
20 this._render();
21 }
22
23 constructor() {
24 super();
25 [Link] = [Link]({ mode: 'open' });
26 [Link]([Link](true));
27 [Link] = [Link]('section');
28 }
29
30 _render() {
31 this._characters.forEach(character => {
32 const button = [Link]('button');
33 [Link]([Link]([Link]));
34 [Link]('click', () => [Link](new CustomEvent('sel\
35 ectCharacter', { detail: character })));
36 [Link](button);
37 });
38 }
39 }
40
41 [Link]('x-character-list', XCharacterList);

Ok, so lets walk through this component piece by piece. First we have our declared template:

1 const template = [Link]('template');


2 [Link] = `
3 <style>
4 button {
5 display: block;
6 padding: 12px;
7 width: 100%;
8 }
9 </style>
10 <section></section>
11 `;

Our template has some minor styles for the buttons we will be creating as well as a section tag for
Chapter 6 - Component Hierarchy and Architecture 47

us to select and add buttons to. Next is the class definition:

1 get characters() {
2 return this._characters;
3 }
4
5 set characters(value) {
6 this._characters = value;
7 this._render();
8 }
9
10 constructor() {
11 super();
12 [Link] = [Link]({ mode: 'open' });
13 [Link]([Link](true));
14 [Link] = [Link]('section');
15 }

We have a single custom property characters. We have our getter and setter for this property. The
constructor does the typical boilerplate setup we have seen in our previous examples. We create
a reference to the section element for us to generate the list of buttons. Notice in the setter for
characters we call this._render(). Let’s go ahead and take a look at the _render() method.

1 _render() {
2 this._characters.forEach(character => {
3 const button = [Link]('button');
4 [Link]([Link]([Link]));
5 [Link]('click', () => [Link](new CustomEvent('selec\
6 tCharacter', { detail: character })));
7 [Link](button);
8 });
9 }

Our render method is called any time the characters property is updated. When the render method
runs we loop through our given characters creating our buttons. Because we have no built-in
templating support, we have to manually create the DOM nodes, buttons and then add each click
event to each button.
In the click event, we trigger an output custom event selectCharacter. As you can see, templates
can get very tedious to maintain without a proper templating system. In a later chapter, we will
cover a few options that make it significantly easier to create dynamic templates and events.
Chapter 6 - Component Hierarchy and Architecture 48

Character Detail
The x-character-detail component will have the responsibility of rendering the details about a
particular character in our application. Let’s take a look at the code.

1 class XCharacterList extends HTMLElement {


2 get character() {
3 return this._character;
4 }
5
6 set character(value) {
7 this._character = value;
8 this._render();
9 }
10
11 constructor() {
12 super();
13 [Link] = [Link]({ mode: 'open' });
14 }
15
16 _render() {
17 [Link] = `
18 <h2>${[Link]}</h2>
19 <ul>
20 <li>Height: ${[Link]}</li>
21 <li>Mass: ${[Link]}</li>
22 <li>Birth Year: ${[Link].birth_year}</li>
23 </ul>
24 `;
25 }
26 }
27
28 [Link]('x-character-detail', XCharacterList);

Starting with the class definition, we can see we have the one input property, character. Whenever
the property changes we call the _render() method. Because this template has no dynamic event
handlers like our list component, it is quite a bit simpler.
Chapter 6 - Component Hierarchy and Architecture 49

1 _render() {
2 [Link] = `
3 <h2>${[Link]}</h2>
4 <ul>
5 <li>Height: ${[Link]}</li>
6 <li>Mass: ${[Link]}</li>
7 <li>Birth Year: ${[Link].birth_year}</li>
8 </ul>
9 `;
10 }

Looking back at our original diagram we can see how we can leverage input properties and output
events to accomplish component to component communication in a decoupled way.
Chapter 6 - Component Hierarchy and Architecture 50

Data flow with Web Components

Now our detail component has a single responsibility of rendering the details of one character. This
decouples the component from its data source. By decoupling the component we make it more
reusable and testable.
The idea of pure rendering components is a common pattern with many component based JavaScript
frameworks. This is especially important for Web Components since it is likely that our Web
Components are going to be mixed with other opinionated JavScript frameworks. Keeping the
components generic or render only will make them easier to integrate into existing applications.
In our next chapter, we will learn the basics of how to bundle and publish our web components for
other developers to use.
Chapter 7 - Production Ready(ish)
Web Components
In this chapter, we are going to go over a brief overview of what it takes to publish Web Components.
The chapter is named “Production Ready(ish)” because while this chapter introduces what it takes
to publish components, we will later cover additional tooling that automates much of the work we
will cover in this chapter. This chapter is to emphasize the reasons we still need a build process to
distribute Web Components with cross-browser support.
We have covered the basics of how to make a Web Component but to make it useful we need to be
able to use it in multiple applications. We will include the simplest way to publish our component
for others to use and then slowly add more complexity to cover more use cases.

Publishing with NPM


With our first example we will make a few assumptions about how our Web Component will be
used. First, we are supporting only browsers that support the core Web Component APIs. We will
cover later how we can get better broad browser support. We will also assume that the consumers of
our Web Component will use the Node Package Manager (NPM) to install and use our component.
In our example we will have a basic folder structure like the following:

Basic Web Component Library


Chapter 7 - Production Ready(ish) Web Components 52

In our [Link] we have the dropdown component we built in our previous chapter. In the
[Link] we re-export the component and any other components we might have in our library.

1 // [Link]
2 export * from './dropdown';

To publish to NPM, we need to first create our [Link] file. The [Link] file defines
some metadata about our package as well as any other dependencies it may have on other NPM
packages.
To create our [Link] we will need to have NodeJS⁵ installed. Once installed the NPM
command line/terminal CLI will be available for us to use. To generate the [Link], we will
run the following command in the directory of our component library:

1 npm init

Running this command will prompt us with a few basic settings such as the package name, version,
and license. Once that is done we will need to add some additional information.

1 {
2 "name": "web-component-essentials",
3 "version": "0.0.1",
4 "description": "",
5 "main": "src/[Link]",
6 "module": "src/[Link]",
7 "directories": {
8 "src": "src"
9 },
10 "author": "Cory Rylan",
11 "license": "MIT"
12 }

In the [Link] we define two new properties, main and module. We use these to help package
bundlers like Webpack understand how to consume our package and where the entry point of our
library is. This is the basic setup for a simple NPM package.
Now that we have the minimal setup we can publish our component by running:

1 npm publish
⁵[Link]
Chapter 7 - Production Ready(ish) Web Components 53

Assuming you have an NPM account and are logged in, your component should be success-
fully published to the NPM registry. Here is where our demo dropdown component is located
[Link]
To use our component in a web project we have a couple of options. Typically we would have some
build system or asset bundler like Webpack. For this first demo, we are going to keep it simple with
a good old script tag. That’s right, no fancy Webpack config or build step, just a single script tag in
an HTML page. Let’s take a look.

1 <!DOCTYPE HTML>
2
3 <html>
4
5 <head>
6 <title>Web Component Essentials</title>
7 </head>
8
9 <body>
10 <x-dropdown>
11 Hello From Published Component
12 </x-dropdown>
13 <script type="module" src="[Link]
14 [Link]"></script>
15 </body>
16
17 </html>

In our [Link] we have a reference to the x-dropdown and a single script tag. We can use the
[Link]⁷ CDN to easily use our published component without any build steps. If we take a look
at our running web page we see the following:
⁶[Link]
⁷[Link]
Chapter 7 - Production Ready(ish) Web Components 54

Publish Web Component

Congratulations, you have created and published your first Web Component! Even though we have
gotten this far, there is still a lot to learn. In our next sections, we will learn about advanced tools
to handle more complex Web Components and get the optimal browser support needed for our
components.

Browser Support
Browser Support for Web Components is not yet complete. Currently, all Webkit and Chromium
browsers (Chrome, Safari, Opera, Brave) support the Web Component APIs. Firefox recently just
shipped the APIs necessary for Web Components as well in version 63. This leaves just iE11 and
Edge. Edge recently announced that they are actively developing for the Custom Elements API and
Shadow DOM API. In the near future when Edge adds support this will leave only IE11 if you need
to support IE at all. Luckily most of the features we have covered have polyfills or fallbacks the
browsers that do not yet support all the Web Component features.
There are two challenges with shipping Web Components to browsers that do not yet support them.
The first is lack of ES2015 JavScript support. Older browsers, yes looking at you IE, don’t support
some of the latest JavaScript features like Classes that our Custom Element definitions depend on.
To get the optimal browser support, we need to add a build step to our code base that can transpile
our code. Transpiling code is similar to a compiler; we are taking our ES2015 code and converting
to an older ES5 code that all browsers support, even IE.
There are a couple of common ways to transpile our JavaScript code from ES2015 to ES5 code. The
two most common ways to achieve this are via Babel and TypeScript. Babel takes ES2015 code and
can transpile down to ES5. TypeScript has similar transpiling support as Babel but also adds static
typing to our JavaScript making it much more maintainable and testable. We will go into TypeScript
in a later chapter. This section we are going to stick with vanilla JavaScript and use Babel to change
our ES2015 code to ES5.
Chapter 7 - Production Ready(ish) Web Components 55

Polyfills
All modern evergreen browsers have ES2015 JavaScript support. This allows us to use new language
features such as Classes and Modules. While almost all browsers excluding IE support ES2015, not
all browsers support the Web Component APIs. Some of these APIs include Custom Elements,
Templates, Shadow DOM, and CSS Custom Properties. Fortunately, we can polyfill some of these
features so we can still create Web Components and get cross-browser support.
To achieve the desired browser support, we will use the Web Component JS Polyfills⁸. These polyfills
include the essential Web Component API features we need. Going back to the dropdown component
we published in our previous section worked just fine in Chrome. Let’s go ahead and open the
example in Edge (version 17).

Broken Web Component

As you can see our component fails to be created in this version of Edge. This is because this version
of Edge (version 17) does not yet support some of the Web Component APIs. To fix this let’s add the
following code to our [Link] file.

1 <!DOCTYPE HTML>
2
3 <html>
4
5 <head>
6 <title>Web Component Essentials</title>
7 <meta name="viewport" content="width=device-width, initial-scale=1">
8 <link href="/[Link]" rel="stylesheet" />
9 </head>
10
11 <body>
12 <header>
⁸[Link]
Chapter 7 - Production Ready(ish) Web Components 56

13 <h1>Example 14 Browser Support</h1>


14 <a href="/">Back to examples</a>
15 </header>
16
17 <main>
18 <x-dropdown>
19 Hello From Published Component
20 </x-dropdown>
21 </main>
22 <!-- Browsers that do not support Web Components APIs -->
23 <script src="[Link]
24 [Link]"></script>
25
26 <!-- ES2015+ version of our library for evergreen browsers -->
27 <script type="module" src="[Link]
28 [Link]"></script>
29 </body>
30
31 </html>

We are adding the @webcomponents polyfill bundle which fills in the missing features that we need
for our component to work. If we save and go back to Edge we should see our component working.
Chapter 7 - Production Ready(ish) Web Components 57

Polyfilled Web Component working in Edge

Polyfills can help us get the cross-browser support that we need, but many of us need to still worry
about older browsers specifically IE11. To support IE11 we will need the same polyfills but also
another build step to convert our ES2015 code to ES5 code that IE can understand.
In our library containing the dropdown component, we are going to add some tools to our project to
compile our ES2015 code to ES5. The two primary tools we will be using in this section are Webpack
and Babel. Webpack is a tool that packages dependencies together for web applications. Babel is a
compiler that can take our ES2015 JavaScript and compile it out to ES5 code that we need for IE11.
This section will be a brief introduction to these tools but by no means is a comprehensive overview.
One could easily have multiple books written about the in-depth technical topics of Babel and
Webpack. We will focus on the minimal work to achieve our IE11 support then in a later section
cover high-level component authoring tools to make this work even easier.

Installing Webpack and Babel


The first step to transpiling our code is to install both Webpack⁹ and Babel¹⁰. To install in our previous
project, we will run the following command.

⁹[Link]
¹⁰[Link]
Chapter 7 - Production Ready(ish) Web Components 58

1 npm install rimraf webpack webpack-cli babel-core babel-loader babel-preset-env --sa\


2 ve-dev

This will install several packages needed for our build step. We need to make a few changes to our
[Link] Let’s go ahead and look at that now.

1 {
2 "name": "web-component-essentials",
3 "version": "0.0.4",
4 "description": "",
5 "main": "dist/[Link]",
6 "module": "src/[Link]",
7 "scripts": {
8 "clean": "rimraf dist",
9 "build": "npm run clean && webpack --mode production"
10 },
11 "directories": {
12 "src": "src"
13 },
14 "author": "Cory Rylan",
15 "license": "MIT",
16 "devDependencies": {
17 "rimraf": "^2.6.2",
18 "webpack": "^4.16.5",
19 "webpack-cli": "^3.1.0",
20 "babel-core": "^6.26.3",
21 "babel-loader": "^7.1.5",
22 "babel-preset-env": "^1.7.0"
23 },
24 "dependencies": {}
25 }

In our [Link] we see we have our dependencies for our build process. We also have the
following two entries:

1 "main": "dist/[Link]",
2 "module": "src/[Link]",

The main entry will point build tools to the compiled distributed ES5 code. The module entry will
point build tools to where the ES2015 module code is located. The JavaScript located in the /dist
directory will be compiled from the source code we write in the /src directory. We also added two
new npm scripts to the [Link].
Chapter 7 - Production Ready(ish) Web Components 59

1 "scripts": {
2 "clean": "rimraf dist",
3 "build": "npm run clean && webpack --mode production"
4 }

The clean script will remove any generated code from the previous build. The build script will run
our clean script and then call the webpack command to build our library code. To run the build
command in the command line, we run npm run build. Before we can run the command, we need
to set up some more configuration.

Webpack
Webpack¹¹ is a module/dependency packaging tool for web applications. Webpack makes it easy to
take a large tree of dependencies in our application and package them up in a way that is easy to
distribute and version to a production environment. Webpack can package many different kinds of
assets via plugins. For example if we wanted to use a CSS preprocessor like Less or Sass we could
use a Webpack plugin to keep track of all our Sass files and compile them. Compiling the Sass would
be enabled by a Sass Webpack plugin that will allow Webpack to compile, version and package our
CSS for production. For our use case, we are using Babel to compile and package up our ES2015 code
to ES5.
First, we need to create a [Link] file. This file is where we will configure Webpack to
build and package our component library. Let’s go ahead and jump into the configuration code.

1 var path = require('path');


2
3 [Link] = {
4 entry: './src/[Link]',
5 output: {
6 path: [Link](__dirname, 'dist'),
7 filename: '[Link]',
8 library: 'webComponentEssentials',
9 libraryTarget: 'umd'
10 }
11 };

The file above is our minimal Webpack configuration. The entry property defines the entry point
or main of our library. This file is where Webpack will start and resolve any sub-dependencies
(import statements). The output property defines where the generated/compiled JavaScript should
be located. The output property also defines what the output file name should be and what kind of
JavaScript module to compile to.
¹¹[Link]
Chapter 7 - Production Ready(ish) Web Components 60

Before JavaScript modules existed in browsers, the JavaScript ecosystem had to create makeshift
modules. With this arose many different module formats. Nowadays we can use native JavaScript
modules for our code. For browsers that don’t support native JavaScript modules (IE) we typically
compile them to umd (Universal Module Definition) modules or globals.
Now that we have our config defined we can run our npm build script.
npm run build

If we look in the dist folder, you will see a [Link] file that is compiled to the older ES5 JavaScript
that will be compatible with older browsers like IE11.
There are still issues we have not yet addressed such as dynamically serving different polyfills or
bundles based on browser feature detection or support. This chapter was a brief introduction to
the JavaScript tooling ecosystem when it comes to transpiling and adding browser support. In later
chapters we will introduce Web Component-specific tooling that makes Web Component authoring,
and publishing more straightforward. Before we dig into more tooling our next chapter we will take
our dropdown component that we have built and integrate it into an Angular and VueJS application.
Chapter 8 - Using Web Components in
Angular and VueJS
One of the big reasons to adopt Web Components is the ability to reuse components across a wide
variety of JavaScript frameworks and libraries. In this chapter, we will see an example Angular and
VueJS application that will use our dropdown component.

Angular
Angular¹² has been designed from the ground up to work with Web Components. Angular not
only can consume Web Components but can also publish Angular components as Web Components
via the Angular Elements API. For our example, we will be showing how to install the dropdown
component into an Angular CLI project. You can learn more about the Angular CLI at [Link]¹³.
Let’s get started.
First, we will create an Angular project using the Angular CLI. We will need to install the Angular
CLI by running the following command:

1 npm install -g @angular/cli

This command will install the Angular CLI tooling to our terminal/command line. Once installed
we can run the following command to create our CLI project.

1 ng new my-app

This command will create a CLI project and install all the necessary NPM packages. Once completed
in our CLI project we can run:

1 ng serve

The ng serve command will run our Angular application locally at localhost:4200. Now that we
have our Angular project up and running we need to install our dropdown component. Remember
in our previous chapters we published our component to NPM. In our Angular project we can now
install that component by running:

¹²[Link]
¹³[Link]
Chapter 8 - Using Web Components in Angular and VueJS 62

1 npm install web-component-essentials

This command will install our component to our Angular project and will add an entry into the
[Link]. Once installed in our [Link] we can import the component.

1 import { BrowserModule } from '@angular/platform-browser';


2 import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
3 import 'web-component-essentials';
4
5 import { AppComponent } from './[Link]';
6
7 @NgModule({
8 declarations: [
9 AppComponent
10 ],
11 imports: [
12 BrowserModule
13 ],
14 providers: [],
15 bootstrap: [AppComponent],
16 schemas: [
17 CUSTOM_ELEMENTS_SCHEMA // Tells Angular we will have custom tags in our templates
18 ]
19 })
20 export class AppModule { }

Once imported we need to add CUSTOM_ELEMENTS_SCHEMA from @angular/core to the application


module. The CUSTOM_ELEMENTS_SCHEMA tells Angular that we will be using custom elements that are
not registered Angular components in our application.
Now that our component is installed we can use it in our Angular application. Let’s take a look at
the [Link] file.

1 import { Component } from '@angular/core';


2
3 @Component({
4 selector: 'app-root',
5 templateUrl: './[Link]',
6 styleUrls: ['./[Link]']
7 })
8 export class AppComponent {
9 myTitle = 'project-angular';
10 open = false;
Chapter 8 - Using Web Components in Angular and VueJS 63

11
12 toggle(event) {
13 [Link](event);
14 [Link] = [Link];
15 }
16 }

The App component is the root component of our Angular application. On our component, we will
have two properties. The myTitle which will be passed to the dropdown and the open property to
track if the dropdown is open or closed.
The App component also has a single method toggle() that will be called whenever the dropdown
has opened or closed. Next, let’s look at the [Link] template.

1 <h1>Angular Application using Web Components</h1>


2
3 <p>
4 {{open ? 'open' : 'closed'}}
5 </p>
6
7 <x-dropdown [title]="myTitle" (show)="toggle($event)">
8 Hello from Web Component in Angular!
9 </x-dropdown>

In our template, we have an Angular expression that displays the message open or closed based on
the value of the open property. Angular has two different pieces of syntax for binding to properties
and events. This syntax not only works for Angular components but also Web Components.
The first binding is the property binding syntax. This syntax uses the square braces [title]="myTitle"
to tell Angular what property on the component should be set. Our example we take the myTitle
property value and set the [title] property of the dropdown component.
The second binding syntax is the event syntax. Angular components can listen to DOM events
as well as Angular and Web Component events with this syntax. To bind to a event we use the
parentheses (show)="toggle($event)". In the parentheses, we pass the name of the event we want
to listen to. On the right hand of the binding, we pass a method we want to be executed whenever
the event occurs. If we want to pass the event value to the method, we use the $event keyword to
tell Angular to pass the event value onto the log method.
With everything hooked up we should see an output similar to this:
Chapter 8 - Using Web Components in Angular and VueJS 64

Dropdown Web Component in Angular

Angular is an excellent option for client-side applications as it has a robust API that works well for
large enterprise applications while also adding fantastic Web Component support. Our next section
we will look into how to add our dropdown component to a VueJS application.

VueJS
VueJS¹⁴ is a new JavaScript framework that has recently gained a lot of popularity for its simple API
and easier learning curve. In this section, we will create a VueJS CLI project and add our dropdown
Web Component to the project. You can learn more about the Vue CLI at [Link]¹⁵.
First, we need to install the Vue CLI tool. We can install the Vue CLI by running the following
command:

1 npm install -g @vue/cli

Once installed we can create our project by running the following:

1 vue create my-app

This command will create a basic Vue project as well as installing any dependencies. Once installed
we can install our dropdown by running the following:

1 npm install web-component-essentials

This command installs the dropdown package that we published in an earlier chapter. Once installed
we can now import our dropdown into our Vue application. In the [Link] we can add the following:
¹⁴[Link]
¹⁵[Link]
Chapter 8 - Using Web Components in Angular and VueJS 65

1 import Vue from 'vue'


2 import App from './[Link]'
3 import 'web-component-essentials'
4
5 [Link] = false
6
7 new Vue({
8 render: h => h(App)
9 }).$mount('#app')

To run our Vue application, we can run the following command:

1 npm run serve

This command will start up our Vue app at localhost:8080. Let’s take a look at the [Link]
component. Vue components use a single file style of organization. For example, Angular compo-
nents have a TypeScript, HTML and CSS file. Vue components have a single file that contains all
three parts of the component. We will start with the template first.

1 // [Link]
2 <template>
3 <div>
4 <h1>VusJS Application using Web Components</h1>
5
6 <p>
7 {{show ? 'open' : 'closed'}}
8 </p>
9
10 <x-dropdown :title="myTitle" @show="log">
11 Hello from Web Component in Vue!
12 </x-dropdown>
13 </div>
14 </template>

The template of our Vue component looks very similar to our previous Angular example. We can
see an expression that shows if the dropdown is open or closed, {{show ? 'open' : 'closed'}}.
On the dropdown component, we are using Vue’s binding syntax. Just like Angular, we can bind to
properties and events.
To bind to a property, we use the : character. To bind a property to the dropdown title property, we
write :title="myTitle". On our Vue component, we will have a myTitle property that will have its
value assigned to the title of the dropdown component.
Chapter 8 - Using Web Components in Angular and VueJS 66

To listen to events, we use the @ character. Our dropdown has a single event show. To listen to this
event, we write @show="log". This event binding will call the log method on our Vue component
whenever the show event occurs.
Next, let’s look at the Vue component JavaScript.

1 <script>
2 export default {
3 name: 'HelloWorld',
4 data: function () {
5 return {
6 myTitle: 'project-vue',
7 show: false
8 }
9 },
10 methods: {
11 log: function (event) {
12 [Link](event);
13 [Link] = [Link];
14 }
15 }
16 }
17 </script>

The Vue component definition has data and method properties we want to bind on our Vue template.
In our example, we have the two data properties, myTitle and show. We have a single method log
which we saw being bound to the @show event.
If everything is hooked up correctly we should see something similar to this in the browser:
Chapter 8 - Using Web Components in Angular and VueJS 67

Dropdown Web Component in VueJS

VueJS is a great lightweight option to build JavaScript applications that work well with Web
Components. Our next few chapters we will cover Web Component-specific tooling that makes
Web Component authoring, and publishing more straightforward and more manageable.
Chapter 9 - Using Web Components in
React
In our previous chapter, we saw how easy it is to reuse Web Components in JavaScript frameworks
like Angular and VueJS. This chapter we will cover how to integrate a Web Component into the
React component library.
React is a JavaScript library made by Facebook that allows developers to compose UIs with
components. React was the first JavaScript library/framework to popularize component driven
architecture. React was also created before the Web Components APIs were standardized. Because
of this, React does not have broad support for Web Components like the majority of other JavaScript
libraries and frameworks.

React Compatibility
React uses a similar mechanism for component communication by passing properties and functions
as events between components. Unfortunately, the React event system is a synthetic system that does
not use the built-in browser events. This synthetic system means Web Component events cannot
communicate with React components. React, and the JSX templating syntax it uses treats all custom
element properties as attributes incorrectly forcing React users only to use string values.
To overcome these shortcomings in our example, we will show how we can create thin React wrapper
components around our Web Components. Wrapper components will allow React to be able to
become compatible with our Web Components.

Create React App


To demonstrate Web Components in React, we will use the Create React App¹⁶ CLI tool to easily
create a React application. To create our app, we run the following commands:

1 npx create-react-app my-app


2 cd my-app
3 npm start

Once created we will have a full running React application. Now we need to install from NPM our
Web Component just like in our previous examples.
¹⁶[Link]
Chapter 9 - Using Web Components in React 69

1 npm install web-component-essentials --save

In our React application, we will need to create a React Dropdown component to wrap our existing
x-dropdown component.

1 import React, { Component } from 'react';


2 import 'web-component-essentials';
3
4 export class Dropdown extends Component {
5 render() {
6 return (
7 <x-dropdown>
8 {[Link]}
9 </x-dropdown>
10 )
11 }
12 }

To use our x-dropdown, we import the package into the [Link] React component. In the render
function, we add {[Link]} to pass child elements into our content slot.

Properties and Events


We need to map the Web Component properties and events to our React version of the component.
We need to use the componentDidMount() lifecycle hook.

1 import React, { Component } from 'react';


2 import 'web-component-essentials';
3
4 export class Dropdown extends Component {
5 componentDidMount() {
6 [Link]['dropdown'].title = [Link];
7
8 [Link]['dropdown'].addEventListener('show', (e) => {
9 if ([Link]) {
10 [Link](e)
11 }
12 });
13 }
14
15 render() {
Chapter 9 - Using Web Components in React 70

16 return (
17 <x-dropdown ref="dropdown">
18 {[Link]}
19 </x-dropdown>
20 )
21 }
22 }

Using the refs API, we can grab a DOM reference to our x-dropdown. Using this reference, we can
create our event listener. In our event listener we can call any passed functions to our onShow prop
for our react component. This will allow our Web Component to be able to communicate with other
React components. We also assign the title prop of our React dropdown to our Web Component
property.

1 [Link]['dropdown'].title = [Link];

Prop Updates
Next, we need to add additional code for whenever one of the props on our React dropdown change.
To listen for prop updates we can use the componentWillReceiveProps() lifecycle hook.

1 import React, { Component } from 'react';


2 import 'web-component-essentials';
3
4 export class Dropdown extends Component {
5 componentDidMount() {
6 [Link]['dropdown'].title = [Link];
7 [Link]['dropdown'].addEventListener('show', (e) => {
8 if ([Link]) {
9 [Link](e)
10 }
11 });
12 }
13
14 componentWillReceiveProps(props) {
15 if ([Link] !== [Link]) {
16 [Link]['dropdown'].title = [Link];
17 }
18
19 if ([Link] !== [Link]) {
20 [Link]['dropdown'].show = [Link];
Chapter 9 - Using Web Components in React 71

21 }
22 }
23
24 render() {
25 return (
26 <x-dropdown ref="dropdown">
27 {[Link]}
28 </x-dropdown>
29 )
30 }
31 }

Using componentWillReceiveProps() we can check when props are updated and efficiently update
the properties on our Web Component. Now that we have mapped React props to our Web
Component properties and events we can use the Dropdown React component.

1 import React, { Component } from 'react';


2 import './[Link]';
3 import { Dropdown } from './[Link]';
4
5 class App extends Component {
6 constructor(props) {
7 super(props);
8 [Link] = {
9 show: false,
10 title: 'project-react'
11 };
12
13 [Link] = [Link](this);
14 }
15
16 render() {
17 return (
18 <div>
19 <h1>React Application using Web Components</h1>
20
21 <p>
22 {[Link] ? 'open' : 'closed'}
23 </p>
24
25 <Dropdown title={[Link]} onShow={[Link]}>
26 Hello from dropdown
27 </Dropdown>
Chapter 9 - Using Web Components in React 72

28 </div>
29 );
30 }
31
32 handleShow(e) {
33 [Link]({ show: [Link] });
34 }
35 }
36
37 export default App;

Now we should see our rendered Web Component working in a React application.

Dropdown Web Component in React

In our App component, you can see the syntax is not much different than our Angular and Vue
examples. Unfortunately due to the incompatibility of React with the custom elements API we have
to add a thin compatibility layer between our component.
Hopefully soon React will be able to adapt and become compatible with the custom elements API. To
follow the status of the open React issues related to Web Components check out custom-elements-
[Link]¹⁷.
¹⁷[Link]
Chapter 10 - lit-html templates
So far in this book, we have been exclusively working with the low level Web Component APIs.
These APIs are designed to be low level and for Framework and library authors to quickly build
on top of. In the next few chapters, we are going to cover a few different options when it comes to
authoring our Web Components. In this chapter, we are going to look into an advanced templating
solution called lit-html.
The lit-html library and its companion, lit-element are lightweight libraries that make it easier to
write and use Web Components. Lit-html was created and is maintained by the Polymer¹⁸ team
at Google. Lit-html is not the Polymer Web Component library but a new experimental version of
sorts. Let’s get started!

lit-html and template literal strings


Lit-html is a lightweight templating library built on top of tagged template literals. Tagged templates
are special functions attached to JavaScript template literal strings (the backtick symbol ‘). Template
literals allow us to create a powerful template syntax right in the browser. Alongside with lit-html,
the polymer team created the lit-element base class to make it a bit easier to create Web Components.
In this example we are going to take our dropdown component we created in the previous chapters
and convert it to use lit-html and lit-element. Let’s start with the minimal amount of code to create
our component.

1 import { LitElement, html } from '@polymer/lit-element';


2
3 class XDropdown extends LitElement {
4 render() {
5 return html`
6 Hello from lit-element
7 `;
8 }
9 }
10
11 [Link]('x-dropdown', XDropdown);

To create our dropdown element, we import the LitElement base class from the @polymer/lit-element
package. Our dropdown extends the LitElement class and registers it just like a standard Custom
¹⁸[Link]
Chapter 10 - lit-html templates 74

Element. Lit-element expects a render() method to be implemented. This render method returns
our template for our component. Our template is created using the tagged template function html
from the lit-html package. The html template function will provide additional templating features
that we will see next.

Templates and Event Listeners


Let’s go ahead and add the template needed to render our dropdown component.

1 import { LitElement, html } from '@polymer/lit-element';


2
3 class XDropdown extends LitElement {
4 render() {
5 return html`
6 <style>
7 .dropdown div {
8 border: 1px solid #ccc;
9 padding: 12px;
10 }
11 </style>
12 <div class="dropdown">
13 <button @click="${() => [Link]()}">${[Link]}</button>
14 ${[Link] ?
15 html`
16 <div>
17 <slot></slot>
18 </div>`
19 : '' }
20 </div>
21 `;
22 }
23
24 toggle() {
25 [Link] = ![Link];
26 }
27 }
28
29 [Link]('x-dropdown', XDropdown);

In the dropdown template, we have the button and slot element that will dynamically toggle our
dropdown content just like our previous chapter examples. Lit-html will automatically create our
Chapter 10 - lit-html templates 75

template using the HTML Template API and Shadow DOM. Using these APIs will allow us to use
CSS Encapsulation.
Lit-html has a declarative API for listening to events. As you can see in the template we have the
following binding:

1 `<button @click="${() => [Link]()}">${[Link]}</button>`

Using the @ character we can tell lit-html what event to bind to and trigger expressions to be called.
Our use case is when the click event occurs, call the toggle() method on the component class.
If we try to run the code as is, the dropdown will not function correctly. We still need to add a few
more things before our component is ready to use.

Properties and Decorators


For lit-html to correctly render our template when a property changes we need to tell lit-html which
properties can change or be set explicitly. To do this, we have two options. The first option allows
us to declare a list of properties that can be set on the component.

1 import { LitElement, html } from '@polymer/lit-element';


2
3 class XDropdown extends LitElement {
4 static get properties() {
5 return {
6 visible: { type: Number },
7 title: { type: String },
8 }
9 }
10
11 constructor() {
12 super();
13 [Link] = false;
14 [Link] = 'dropdown';
15 }
16
17 render() {
18 return html`
19 <style>
20 .dropdown div {
21 border: 1px solid #ccc;
22 padding: 12px;
23 }
Chapter 10 - lit-html templates 76

24 </style>
25 <div class="dropdown">
26 <button @click="${(e) => [Link](e)}">${[Link]}</button>
27 ${[Link] ?
28 html`
29 <div>
30 <slot></slot>
31 </div>`
32 : '' }
33 </div>
34 `;
35 }
36
37 toggle() {
38 [Link] = ![Link];
39 }
40 }
41
42 [Link]('x-dropdown', XDropdown);

The static get properties() will allow lit-html to track when the properties are set and if it should
re-render the component. The other optional API is instead of using a static list of properties you
can use experimental decorators and property initializers. Decorators and property initializers are
proposed language features for JavaScript. If you are using Babel or TypeScript, you can have the
following code instead,

1 import { LitElement, html, property } from '@polymer/lit-element';


2
3 class XDropdown extends LitElement {
4 @property()
5 visible = false;
6
7 @property()
8 title = 'dropdown';
9
10 render() {
11 return html`
12 <style>
13 .dropdown div {
14 border: 1px solid #ccc;
15 padding: 12px;
16 }
17 </style>
Chapter 10 - lit-html templates 77

18 <div class="dropdown">
19 <button @click="${(e) => [Link](e)}">${[Link]}</button>
20 ${[Link] ?
21 html`
22 <div>
23 <slot></slot>
24 </div>`
25 : '' }
26 </div>
27 `;
28 }
29
30 toggle() {
31 [Link] = ![Link];
32 }
33 }
34
35 [Link]('x-dropdown', XDropdown);

Using decorators, you can have a much more declarative syntax. The tradeoff is you will need a
build step to create your components which we lightly covered in the previous chapter.

Custom Events
Events in lit-elements are no different than standard custom element events.

1 import { LitElement, html, property } from '@polymer/lit-element';


2
3 class XDropdown extends LitElement {
4 static get properties() {
5 return {
6 visible: { type: Number },
7 title: { type: String },
8 }
9 }
10
11 constructor() {
12 super();
13 [Link] = false;
14 [Link] = 'dropdown';
15 }
16
Chapter 10 - lit-html templates 78

17 render() {
18 return html`
19 <style>
20 .dropdown div {
21 border: 1px solid #ccc;
22 padding: 12px;
23 }
24 </style>
25 <div class="dropdown">
26 <button @click="${(e) => [Link](e)}">${[Link]}</button>
27 ${[Link] ?
28 html`
29 <div>
30 <slot></slot>
31 </div>`
32 : '' }
33 </div>
34 `;
35 }
36
37 toggle() {
38 [Link] = ![Link];
39
40 // trigger custom event
41 [Link](new CustomEvent('visibleChange', { detail: [Link] }));
42 }
43 }
44
45 [Link]('x-dropdown', XDropdown);

Now we have our properties and events set up we can interact with our component just like any
other Web Component.

1 const dropdown = [Link]('x-dropdown');


2 [Link] = 'custom dropdown';
3 [Link]('visibleChange', (e) => [Link](e));

Binding to other Web Components with lit-html


Lit-html can also be used to interact with other existing Web Components in our applications. In
our previous example, we imperatively created a reference and an event listener for our dropdown.
Chapter 10 - lit-html templates 79

In this next example, we will use lit-html to interact with our dropdown. First, we will create a
top-level app component.

1 import { LitElement, html, property } from '@polymer/lit-element';


2 import 'dropdown';
3
4 class XApp extends LitElement {
5 render() {
6 return html`
7 <x-dropdown>
8 Hello From Lit-HTML!
9 </x-dropdown>
10 `;
11 }
12 }
13
14 [Link]('x-app', XApp);

With our app component, we can use lit-html’s declarative binding to set properties and listen to
events.

1 import { LitElement, html, property } from '@polymer/lit-element';


2 import 'dropdown';
3
4 class XApp extends LitElement {
5 render() {
6 return html`
7 <x-dropdown @visibleChange=${(e) => [Link](e)}>
8 Hello From Lit-HTML!
9 </x-dropdown>
10 `;
11 }
12
13 log(e) {
14 [Link](e);
15 }
16 }
17
18 [Link]('x-app', XApp);

In the template of the app component, we use the same @ binding to listen to events. This binding
works not only with native DOM events but custom events like the visibleChange event. We can
also set properties of our components with lit-html.
Chapter 10 - lit-html templates 80

1 import { LitElement, html, property } from '@polymer/lit-element';


2 import 'dropdown';
3
4 class XApp extends LitElement {
5 constructor() {
6 super();
7 [Link] = 'Custom Title!';
8 }
9
10 render() {
11 return html`
12 <x-dropdown
13 @visibleChange=${(e) => [Link](e)}
14 .title="${[Link]}">
15 Hello From Lit-HTML!
16 </x-dropdown>
17 `;
18 }
19
20 log(e) {
21 [Link](e);
22 }
23 }
24
25 [Link]('x-app', XApp);

With .title="${[Link]}" we use the . to bind to the title property of the dropdown.
This binding allows us to pass data down to the component.
Using lit-html, we can create Web Components with a nice declarative syntax while still keeping
great performance. Our next chapter we will look at a new tool called StencilJS that takes the Web
Component abstraction one step further as a Web Component Compiler.
Chapter 11 - Stencil JS
In our previous chapter, we learned about lit-html and how it gives us an excellent templating system
for our Web Components. This chapter we are going to cover another Web Component authoring
tool called StencilJS¹⁹. Stencil is referred to as a Web Component compiler.
Stencil, unlike lit-html, adds a build process to create its Web Components. Stencil is a more opinion-
ated but provides a higher level API to create Web Components. Stencil leverages technologies like
TypeScript and JSX. Stencil was created and is maintained by Ionic. Ionic is a company specializing
in making it easy to develop native mobile apps and Progressive Web Apps with Web technologies.
Ionic has a rich suite of UI components that are built as Web components allowing developers to
make native mobile apps on IOS and Android but use familiar Web technologies.

Ionic Component Docs

The Ionic UI kit was built initially with Angular components, but recently Ionic created Stencil to
enable them to rewrite their components as Web Components. Doing so has allowed anyone to use
Ionic components and not restrict them to only being used in Angular apps.
¹⁹[Link]
Chapter 11 - Stencil JS 82

In this chapter, we are going to create our dropdown component with Stencil. We will cover the
benefits that stencil provides as a top-down solution for authoring Web Components.

The Stencil CLI


The Stencil CLI is a command line tool that makes it easy to create and build Stencil Component
libraries as well as Progressive Web Apps. The Stencil CLI allows us to write our components with
TypeScript and JSX. The CLI also provides a mechanism to write CSS and Sass in stand alone files.
Lastly the CLI also provides built in unit testing so you can easily write automated tests for your
Web Components. In our example, we will use the Stencil’ CLI to create a component library for
our dropdown component. Let’s get started!
First, we need to run the Stencil CLI by running the following command:

1 npm init stencil

This command will run the stencil CLI and prompt you to choose a project to create. We will want
to select the component library option.
Once completed you will have a Stencil project to start authoring your components in. In the Stencil
project, there will be a src/components directory. The project will start out with a single component
[Link]. Stencil is written with TypeScript and JSX. If you have used React or Angular,
this will feel very familiar. Stencil looks almost like a hybrid of Angular and React.
In the components directory, we will create a [Link] file for our dropdown component.
Let’s go ahead and look at the component code.

1 import { Component, Event, EventEmitter, Prop, State } from '@stencil/core';


2
3 @Component({
4 tag: 'x-dropdown',
5 styleUrl: '[Link]',
6 shadow: true
7 })
8 export class XDropdown {
9 @Prop() title = 'dropdown';
10 @State() show = false;
11 @Event() showChange: EventEmitter;
12
13 render() {
14 return (
15 <div>
16 <button onClick={() => [Link]()}>{[Link]}</button>
Chapter 11 - Stencil JS 83

17 {[Link]
18 ? <div class="x-dropdown__content"><slot /></div>
19 : <div></div>
20 }
21 </div>
22 );
23 }
24
25 toggle() {
26 [Link] = ![Link];
27 [Link]([Link]);
28 }
29 }

Decorators
Stencil uses a combination of decorators and JSX to make a declarative and straightforward way to
create Web Components. First, let’s start with the @Component decorator.

1 @Component({
2 tag: 'x-dropdown',
3 styleUrl: '[Link]',
4 shadow: true
5 })

If you have used Angular, this decorator will look very familiar. Decorators are built into TypeScript
and in the proposal stages for JavaScript. Decorators allow developers to add additional metadata
to classes and properties. Stencil uses these decorators to help create and compile your component
into a Web Component.
The @Component decorator describes our dropdown component. The tag property defines what
the element tag name should be. The styleUrl allows us to define a stand-alone stylesheet for
component level styles. The shadow property allows us to tell Stencil if we want to enable or disable
the Shadow DOM for our component.
Next, in our class definition, we have three properties with decorators.

1 @Prop() title = 'dropdown';


2 @State() show = false;
3 @Event() showChange: EventEmitter;
Chapter 11 - Stencil JS 84

Each decorator is provided by Stencil and adds behavior to our component. The first decorator @Prop
allows us to define public properties on our component for others to use and pass data around. For
our single property, we have the title to set the dropdown button text.
The second decorator @State allows Stencil to know which properties when updated should trigger
the template to re-render. For our use case whenever the button is clicked we update the show
property and because of the @State decorator, the template will be re-rendered.
The last decorator @Event allows us to create custom events for our component. By applying the
@Event decorator we can call [Link]([Link]); to trigger our showChange event.
The name of the property will be the name of the event emitted.

JSX Templates
Next, in our component class definition, we have the render() method. The render() method
expects us to return a JSX template.

1 render() {
2 return (
3 <div>
4 <button onClick={() => [Link]()}>{[Link]}</button>
5 {[Link]
6 ? <div class="x-dropdown__content"><slot /></div>
7 : <div></div>
8 }
9 </div>
10 );
11 }

In the template, we can listen to DOM events and trigger method calls. In our example onClick of
the button we call the toggle() method.

1 <button onClick={() => [Link]()}>{[Link]}</button>

To conditionally render parts of our template we can use a ternary operator.

1 {[Link]
2 ? <div class="x-dropdown__content"><slot /></div>
3 : <div></div>
4 }

Lastly, in our dropdown, we have the toggle() method.


Chapter 11 - Stencil JS 85

1 toggle() {
2 [Link] = ![Link];
3 [Link]([Link]);
4 }

The toggle method inverts the show property and then triggers the custom event to emit. Now that
we have recreated our dropdown in Stencil let’s look at how to use our component in another Stencil
template.

JSX Component Bindings


In the MyComponent that the Stencil CLI created, we are going to update it to use our XDropdown
component.

1 import { Component } from '@stencil/core';


2
3 @Component({
4 tag: 'my-component',
5 styleUrl: '[Link]',
6 shadow: true
7 })
8 export class MyComponent {
9 private myTitle = 'StencilJS';
10
11 render() {
12 return (
13 <div>
14 Hello, World! I'm a Web Component built with Stencil!
15
16 <div>
17 <x-dropdown title={[Link]} onShowChange={(e) => [Link](e)}>
18 Hello from Stencil Dropdown!
19 </x-dropdown>
20 </div>
21 </div>
22 );
23 }
24
25 private log(event: any) {
26 [Link](event);
27 }
28 }
Chapter 11 - Stencil JS 86

In the MyComponent file, we have a simple template that demonstrates how Stencil can bind to Web
Components. Just like we saw in Angular and Vue, Stencil can bind to properties and events.

1 <x-dropdown title={[Link]} onShowChange={(e) => [Link](e)}>


2 Hello from Stencil Dropdown!
3 </x-dropdown>

With stencil by default, binds to properties by naming the property on the component tag. To listen
to custom events, we prefix the event name with on.

Building your Stencil Components


Stencil can be used to build entire applications, but it is designed to allow you to write and easily
publish your Web Components. To build your Web Components, in the root of your Stencil project
run the following command:

1 npm run build

This command will compile our Stencil components into plain Web Components that we can use
anywhere. You can configure where the components are compiled in the [Link] file.
You can find more options on how to distribute your components in the distribution documentation
found here [Link]/docs/distribution²⁰
Stencil is an excellent choice for authoring Web Components as it gives you the convenience of a
framework and all the benefits of Web Components.
²⁰[Link]
Chapter 12 - Building a Todo App with
lit-html
In this chapter, we are going to expand on how to build web apps with Web Components. For our
example, we are going to build a simple todo app that can create, delete and update a todo list by
using lit-html and local storage. This example will also reinforce some of the best practices with
component architecture.
Let’s first take a look at what our todo app will look like and define the requirements.

Todo App

This todo app will save our todos into local storage on the user’s device. We should be able to add,
edit and delete todos. When we double click and existing todo we will switch the text with an input
to update the todo item.
Chapter 12 - Building a Todo App with lit-html 88

Implementation
This example we will use lit-html for our Web Component templates. We will also, use a simple
Webpack build to compile our application with TypeScript.
Let’s start with the [Link] file.

1 <!DOCTYPE HTML>
2
3 <html>
4
5 <head>
6 <title>Todo App with lit-html</title>
7 <meta name="viewport" content="width=device-width, initial-scale=1">
8 <link href="/[Link]" rel="stylesheet" />
9 </head>
10
11 <body>
12 <main>
13 <x-app></x-app>
14 </main>
15 <script src="dist/[Link]"></script>
16 </body>
17
18 </html>

The [Link] will be pretty simple. Like most component is driven Web apps we will have a single
root entry point component. Many JavaScript frameworks follow similar conventions and name the
component “root” or “app”. For our entry component, we will call it x-app. Our components will be
bundled into a single JavaScript file by Webpack and included at the bottom script tag.
The first thing we need to do is define our root component x-app. Let’s take a look at the component
in [Link].

1 // [Link]
2 import { LitElement, html } from '@polymer/lit-element';
3 import './todos';
4
5 class XApp extends LitElement {
6 constructor() {
7 super();
8 }
9
Chapter 12 - Building a Todo App with lit-html 89

10 render() {
11 return html`
12 <header>
13 <h1>Todos</h1>
14 </header>
15 <main>
16 <x-todos></x-todos>
17 </main>
18 <footer>
19 2018
20 </footer>
21 `;
22 }
23 }
24
25 [Link]('x-app', XApp);

Our root component defines the basic structure of our application. If our app had a more elaborate
header or navigation, it would likely go here. In the main of our app, we have a single component
x-todos. We import this component at the top of our file. The x-todos component will be responsible
for managing the todos in our application.

Todos Data Service


Before we jump into the x-todos component, we need to make a service class that handles saving
and retrieving our todo items from local storage. We will create a file called [Link] this
file will be responsible for any changes to our todos and persisting those in local storage for us. Let’s
go ahead and take a look.

1 // [Link]
2 import { Todo } from "./interfaces";
3
4 export class TodoService {
5 getTodos() {
6 // we use [Link] as local storage only allow key value string pairs
7 const todos: Todo[] = [Link]([Link]('todos'));
8 return todos ? todos : [];
9 }
10
11 // TypeScript interfaces allow us to define what types we expect
12 updateTodo(todo: Todo, index: number) {
13 const todos = [Link]();
Chapter 12 - Building a Todo App with lit-html 90

14 todos[index] = todo;
15 return [Link](todos);
16 }
17
18 deleteTodo(todo: Todo) {
19 const todos = [Link]();
20 const updatedTodos = [Link](t => [Link] !== [Link]);
21 return [Link](updatedTodos);
22 }
23
24 createTodo(value: string) {
25 const todos = [Link]();
26 const updatedTodos = [...todos, { completed: false, value: value }];
27 return [Link](updatedTodos);
28 }
29
30 private saveTodos(updatedTodos: Todo[]) {
31 [Link]('todos', [Link](updatedTodos));
32 return updatedTodos;
33 }
34 }

Our todo service abstracts all the saving and updating logic into a standalone class that way we can
keep our components focused on just rendering the data. Best practice with Web apps is to have a
clean separation of data logic and render logic between components. This separation typically makes
components and business logic easier to unit test.
Our service was also using a TypeScript interface Todo. This interface allows use to define a contract
of how our todo data is shaped.

1 // [Link]
2 export interface Todo {
3 completed: boolean;
4 value: string;
5 }

Now that we have the update logic defined for our todo app let’s take a look at the x-todos
component.

Todos List
The x-todos component will communicate directly with our todos service to save and update our
data.
Chapter 12 - Building a Todo App with lit-html 91

1 // [Link]
2 import { LitElement, html, property } from '@polymer/lit-element';
3 import { TodoService } from './[Link]';
4 import { Todo } from './interfaces';
5 import './todo-item';
6
7 class XTodos extends LitElement {
8 @property()
9 todos: Todo[] = [];
10
11 todoService = new TodoService();
12
13 constructor() {
14 super();
15 [Link] = [Link]();
16 }
17 }
18
19 [Link]('x-todos', XTodos);

We have created our component which will have a todos property using the @property decorator
from lit-html. The @property decorator will allow lit-html to know to re-render the list if it changes.
We also create an instance of our todo service to use in our component. In the constructor, we assign
any existing todos from local storage to the todos property. Next, we can add the template of the
component.

1 import { LitElement, html, property } from '@polymer/lit-element';


2 import { TodoService } from './[Link]';
3 import { Todo } from './interfaces';
4 import './todo-item';
5
6 class XTodos extends LitElement {
7 @property()
8 todos: Todo[] = [];
9
10 todoService = new TodoService();
11
12 constructor() {
13 super();
14 [Link] = [Link]();
15 }
16
17 render() {
Chapter 12 - Building a Todo App with lit-html 92

18 return html`
19 <form @submit="${(e: Event) => [Link](e)}">
20 <input placeholder="add todo item" aria-label="add todo item" />
21 <button>Add</button>
22 </form>
23
24 <ul>
25 ${[Link]((t, i) => html`
26 <li>
27 <x-todo-item
28 .todo="${t}"
29 @update="${(e: CustomEvent) => [Link]([Link], i)}"
30 @delete="${(e: CustomEvent) => [Link]([Link])}">
31 </x-todo-item>
32 </li>`)}
33 </ul>
34 `;
35 }
36 }
37
38 [Link]('x-todos', XTodos);

The first part of our template defines an HTML form element for us to create our todos with. By
using a form element, we can trigger submit events with a button element as well as using the enter
key. On submit of the form we call the createTodo() method which we will see in a bit.
The second part of our template renders the list of todos. A single x-todo-item component renders
each todo. The x-todo-item contains the logic for when the user clicks done, delete or updates the
todo value. Whenever one of these user actions occurs the x-todo-item will emit the @update or
@delete events so our list component can adequately update the todo with the todo service.

1 <x-todo-item
2 .todo="${t}"
3 @update="${(e: CustomEvent) => [Link]([Link], i)}"
4 @delete="${(e: CustomEvent) => [Link]([Link])}">
5 </x-todo-item>

Our x-todo-item is a pure rendering component. It takes in a todo object by binding it to the .todo
property. If anything changes with that object it emits the updates to the parent list. This isolation
makes the logic easier to maintain and makes the x-todo-item more generic and reusable. This
component is the similar pattern we discussed in our earlier chapters with component architecture
best practices.
Next are the methods that are called when the template events occur.
Chapter 12 - Building a Todo App with lit-html 93

1 // [Link]
2 import { LitElement, html, property } from '@polymer/lit-element';
3 import { TodoService } from './[Link]';
4 import { Todo } from './interfaces';
5 import './todo-item';
6
7 class XTodos extends LitElement {
8 @property()
9 todos: Todo[] = [];
10 todoService = new TodoService();
11
12 constructor() {
13 super();
14 [Link] = [Link]();
15 }
16
17 render() {
18 return html`
19 <style>
20 ul {
21 margin: 0;
22 padding: 0;
23 }
24
25 li {
26 list-style: none;
27 margin: 0 0 16px 0;
28 }
29
30 input {
31 margin-bottom: 12px;
32 }
33 </style>
34
35 <form @submit="${(e: Event) => [Link](e)}">
36 <input placeholder="add todo item" aria-label="add todo item" />
37 <button>Add</button>
38 </form>
39
40 <ul>
41 ${[Link]((t, i) => html`
42 <li>
43 <x-todo-item
Chapter 12 - Building a Todo App with lit-html 94

44 .todo="${t}"
45 @update="${(e: CustomEvent) => [Link]([Link], i)}"
46 @delete="${(e: CustomEvent) => [Link]([Link])}">
47 </x-todo-item>
48 </li>`)}
49 </ul>
50 `;
51 }
52
53 updateTodo(todo: Todo, index: number) {
54 [Link] = [Link](todo, index);
55 }
56
57 deleteTodo(todo: Todo) {
58 [Link] = [Link](todo);
59 }
60
61 createTodo(e: any) {
62 // prevent the submit event from triggering a POST
63 [Link]();
64
65 const input = [Link]('input');
66 const todo = [Link];
67
68 if ([Link] > 0) {
69 [Link] = [Link](todo);
70 [Link] = '';
71 }
72 }
73 }
74
75 [Link]('x-todos', XTodos);

As you can see the methods of the list component, for the most part, pass the information along
to the todo service. By using the todo service, the list is now, and the todo service could be used
elsewhere in a more complex application. Next, let’s look at the implementation of the x-todo-item
component.

Todo Item
The x-todo-item component is responsible for rendering a single todo item and emitting and updates
to that particular item. First, we will look at the component definition and template.
Chapter 12 - Building a Todo App with lit-html 95

1 // [Link]
2 import { LitElement, html, property } from '@polymer/lit-element';
3 import { Todo } from './interfaces';
4
5 class XApp extends LitElement {
6 @property()
7 todo: Todo;
8
9 @property()
10 editing = false;
11
12 render() {
13 return html`
14 <button @click="${() => [Link]()}" aria-label="mark done">
15 ✓
16 </button>
17
18 ${[Link] ?
19 html`
20 <form @submit="${(e: any) => [Link](e)}">
21 <input .value="${[Link]}" aria-label="create todo" placeholder="t\
22 odo" />
23 </form>
24 `:
25 html`
26 <span class="${[Link] ? 'completed' : ''}" @dblclick="${() => t\
27 [Link]()}">
28 ${[Link]}
29 </span>
30 `}
31
32 <button @click="${() => [Link]()}" aria-label="delete todo">
33 x
34 </button>
35 `;
36 }
37 }
38
39 [Link]('x-todo-item', XApp);

For this component, we will have two properties. One property will be for the todo object and the
second property is for tracking if the todo is being edited. Our template has two buttons one to mark
the todo complete as well as one to delete the todo. In the template, we have a ternary condition
Chapter 12 - Building a Todo App with lit-html 96

that will show a form to edit the todo if the todo has been double-clicked. Note we can also bind to
CSS classes as well. When a todo is marked complete, we can add a CSS class to add a strikethrough
property on the todo.

1 import { LitElement, html, property } from '@polymer/lit-element';


2 import { Todo } from './interfaces';
3
4 class XApp extends LitElement {
5 @property()
6 todo: Todo;
7
8 @property()
9 editing = false;
10
11 render() {
12 return html`
13 <style>
14 .completed {
15 text-decoration: line-through;
16 }
17
18 span {
19 padding: 0 12px;
20 }
21
22 form {
23 display: inline-block;
24 }
25 </style>
26
27 <button @click="${() => [Link]()}" aria-label="mark done">
28 ✓
29 </button>
30
31 ${[Link] ?
32 html`
33 <form @submit="${(e: any) => [Link](e)}">
34 <input .value="${[Link]}" aria-label="create todo" placeholder="t\
35 odo" />
36 </form>
37 `:
38 html`
39 <span class="${[Link] ? 'completed' : ''}" @dblclick="${() => t\
Chapter 12 - Building a Todo App with lit-html 97

40 [Link]()}">
41 ${[Link]}
42 </span>
43 `}
44
45 <button @click="${() => [Link]()}" aria-label="delete todo">
46 x
47 </button>
48 `;
49 }
50
51 completeTodo() {
52 [Link] = { ...[Link], completed: ![Link] };
53 [Link]();
54 }
55
56 deleteTodo() {
57 [Link](new CustomEvent('delete', { detail: [Link] }));
58 }
59
60 editTodo(e: any) {
61 [Link]();
62
63 const input = [Link]('input');
64 const todo = [Link];
65
66 if ([Link] > 0) {
67 [Link] = todo;
68 [Link]();
69 [Link]();
70 }
71 }
72
73 toggleForm() {
74 [Link] = ![Link];
75 }
76
77 private emitUpdate() {
78 [Link](new CustomEvent('update', { detail: [Link] }));
79 }
80 }
81
82 [Link]('x-todo-item', XApp);
Chapter 12 - Building a Todo App with lit-html 98

The methods on the todo item update our todo object. Note we create a new object when updating
a todo as our data must be immutable for lit-html to re-enter our templates. Whenever an update or
a todo gets deleted, we emit a custom event to the todos list.
Chapter 13 - Conclusion
Web Components are still a new technology but promise a lot of potential for the future of the
Web. This book will be continually updated with new chapters and code examples. Please check
back often for updates. Keep up to date with new content about Web Components subscribe at
[Link]²¹.

²¹[Link]

You might also like