Web Components & Web Workers

SagarTS
readytowork, Inc.
Published in
6 min readJul 27, 2022

Web Components

Set of web platform APIs that allow us to create custom, reusable, and encapsulated HTML tags to use in web pages and web apps.

Web components do not require any special 3rd party libraries or frameworks but can certainly be used with them. The core concept of a Web Component is similar to that of components in frameworks such as React, Angular, or Vue. It is a reusable UI building block that encapsulates all the HTML, CSS, and JavaScript-based logic required to render it. The big difference is that instead of relying on a specific JavaScript framework it leverages technologies natively provided by the browser so that your Web Components are framework agnostic.

Benefits of Web Components

Web Components can be a great solution for companies that use different JavaScript frameworks across different sites and would like to keep the look and feel of those sites consistent.

Web Components can also be good for future-proofing your site. For example, say that your site currently uses React and you have built all your components in React. Then in the future, either due to business decisions, technology advances, or maintainability issues, your company decides to switch to a different JavaScript framework. In that case, your company would have to develop new components in this new framework. Alternatively, if your company’s component library was built using Web Components, that library can be re-used in the new JavaScript framework thus saving costs and time.

3 Main Building Blocks

  1. Custom Elements

A set of JavaScript APIs that allow you to define custom elements and their behavior, which can then be used as desired in your user interface. We can create our own custom HTML tags and add custom behavior to them.

Use JavaScript to define a new HTML element and its tag with the customElements global. Call customElements.define() with the tag name you want to create and a JavaScript class that extends the base HTMLElement.

For example, to define a user card, <user-card>:

class UserCard extends HTMLElement { … } window.customElements.define(‘user-card’, UserCard)

To use our custom tag:

<user-card> … </user-card>

2. Shadow DOM

A set of JavaScript APIs for attaching an encapsulated “shadow” DOM tree to an element — which is rendered separately from the main document DOM — and controlling associated functionality. In this way, you can keep an element’s features private, so they can be scripted and styled without the fear of collision with other parts of the document.

Shadow DOM is designed as a tool for building component-based apps. Therefore, it brings solutions to common problems in web development:

  • Isolated DOM: A component’s DOM is self-contained (e.g. document.querySelector() won’t return nodes in the component’s shadow DOM).
  • Scoped CSS: CSS defined inside shadow DOM is scoped to it. Style rules don’t leak out and page styles don’t bleed in.
  • Composition: Design a declarative, markup-based API for your component.
  • Simplifies CSS — Scoped DOM means you can use simple CSS selectors, more generic id/class names, and not worry about naming conflicts.
  • Productivity — Think of apps in chunks of DOM rather than one large (global) page.

To create shadow DOM for an element, call element.attachShadow({mode : open})

For example:

const header = document.createElement(‘header’) 
const shadowRoot = header.attachShadow({mode : open}) shadowRoot.innerHTML = ‘<h1>Shadow Dom</h1>’

3. HTML Templates

The <template> and <slot> elements enable you to write markup templates that are not displayed on the rendered page. These can then be reused multiple times as the basis of a custom element’s structure. Slots are used to add custom text.

A Simple Demo App:

Index.html

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Web Components Learning</title>
<style>
h3{
color: purple
}
</style>
</head>
<body>
<h3>Hello World</h3>
<user-card name="boy" avatar="https://randomuser.me/api/portraits/men/1.jpg"><div slot="email">sagar@gmail.com</div><div slot="phone">111-111-1111</div></user-card>
<user-card name="girl" avatar="https://randomuser.me/api/portraits/women/5.jpg"></user-card>
<script src="index.js" ></script>
</body>
</html>

Index.js

const template = document.createElement('template');
template.innerHTML = `
<style>
h3 {
color: coral
}
</style>
<div class='user-card'>
<img />
<div>
<h3></h3>
<div class='info'>
<p><slot name='email' /></p>
<p><slot name='phone' /></p>
</div>
<button id='toggle-info'>Info</button>
</div>
</div>
`
class UserCard extends HTMLElement {
constructor(){
super()
this.showInfo = true;
this.attachShadow({mode: 'open'});
this.shadowRoot.appendChild(template.content.cloneNode(true));
this.shadowRoot.querySelector('h3').innerText= this.getAttribute('name')
this.shadowRoot.querySelector('img').src= this.getAttribute('avatar')
}
toggleInfo(){
this.showInfo = !this.showInfo
const info = this.shadowRoot.querySelector('.info')
const toggleBtn = this.shadowRoot.querySelector('#toggle-info')
if(this.showInfo){
info.style.display = 'block'
toggleBtn.innerText = 'Hide Info'
} else {
info.style.display = 'none'
toggleBtn.innerText = 'Show Info'
}
}
connectedCallback(){
this.shadowRoot.querySelector('#toggle-info').addEventListener('click',() => this.toggleInfo());
}
disconnect
}
window.customElements.define('user-card', UserCard)

Web Workers

JavaScript was designed to run in a single-threaded environment, meaning multiple scripts cannot run at the same time. So, when executing scripts in an HTML page, the page becomes unresponsive until the script is finished.

Let’s take an example

#index.html

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Web Workers</title>
</head>
<body>
<div>
<h1>Web Workers</h1>
<button id="work-btn">Click Me</button>
<button id="btn">Try to click me</button>
<h1 id="random"></h1>
<h1 id="output"></h1>
<script src="script.js"></script>
</div>
</body>
</html>

#script.js

const workBtn = document.querySelector('#work-btn');
workBtn.addEventListener('click',() => {
let final = 0
for(let i = 0; i < 1000000000; i++){
final += 1
}
document.querySelector('#output').innerHTML = final
})
const secondBtn = document.querySelector('#btn')
secondBtn.addEventListener('click', () => {
document.querySelector('#random').innerHTML = 'random'
})

# RESULT

Here, When you click on Click Me button. It takes some time to complete as the operation that it performs is huge. And you cannot click on Try to click me button until the previous operation is completed.

This is where Web Workers come in handy. A web worker is a JavaScript that runs in the background, independently of other scripts, without affecting the performance of the page. You can continue to do whatever you want: clicking, selecting things, etc., while the web worker runs in the background.

Advantages:

  • Helps complex computing
  • Doesn’t block UI
  • Optimize the performance of our program

Disadvantages:

  • Does not have access to Parent object, Window object, and Document Object

Spawning a dedicated worker

Creating a new worker is simple. All you need to do is call the Worker() constructor, specifying the URI of a script to execute in the worker thread

var worker = new Worker('fileName.js');

If the specified file exists, the browser will spawn a new worker thread, which is downloaded asynchronously. The worker will not begin until the file has been completely downloaded and executed. If the path to your worker returns a 404, the worker will fail silently.

Sending messages to and from a dedicated worker

To send a message to the worker, the postMessage method of the worker object is used as shown below.

worker.postMessage("Hello World!");

The onmessage the property uses an event handler to retrieve information from a worker.

worker.onmessage = function(event) { 
alert("Received message " + event.data);
doSomething();
}
function doSomething() {
//do work
worker.postMessage("Work done!");
}

Terminating a worker

If you need to immediately terminate a running worker from the main thread, you can do so by calling the worker’s terminate method:

worker.terminate()

The worker thread is killed immediately.

Handling Errors:

worker.onerror = function (event) {
console.log(event.message, event);
};

Now, let’s see how we can fix the problem that we had at first, using Web Workers:

#index.html — remains the same

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Web Workers</title>
</head>
<body>
<div>
<h1>Web Workers</h1>
<button id="work-btn">Click Me</button>
<button id="btn">Try to click me</button>
<h1 id="random"></h1>
<h1 id="output"></h1>
<script src="script.js"></script>
</div>
</body>
</html>

#script.js

const workBtn = document.querySelector('#work-btn');
workBtn.addEventListener('click',() => {
const myWorker = new Worker('worker.js')
myWorker.postMessage('do work')
myWorker.onmessage = function(e){
document.querySelector('#output').innerHTML = e.data
}
})
const secondBtn = document.querySelector('#btn')
secondBtn.addEventListener('click', () => {
document.querySelector('#random').innerHTML = 'random'
})

#worker.js

onmessage = function(e) {
let final = 0
for(let i = 0; i < 1000000000; i++){
final += 1
}
this.postMessage(final)
}

Happy Coding 🎉

--

--