![]() - update readme with more read/write access explanation and example - update demo page to use .props |
||
---|---|---|
.vscode | ||
assets | ||
demo | ||
src | ||
.gitignore | ||
LICENSE | ||
package-lock.json | ||
package.json | ||
README.md | ||
sample.html |
>>> TL;DR: This provides a minimal vanilla JS base class that aims to reduce the complexity of creating reactive custom elements. See the Quick Start Example
Web Component Base
When you extend the WebComponent
class for your component, you only have to define the template
and properties
. Any change in any property value will automatically cause just the component UI to render.
The result is a reactive UI on property changes.
Table of Contents
- Import via unpkg
- Installation via npm
- Usage
template
vsrender()
- Prop access
- Quick Start Example
- Life-Cycle Hooks
onInit
- the component is connected to the DOM, before view is initializedafterViewInit
- after the view is first initializedonDestroy
- the component is disconnected from the DOMonChanges
- every time an attribute value changes
Import via unpkg
Import using unpkg in your vanilla JS component. We will use this in the rest of our usage examples.
import WebComponent from "https://unpkg.com/web-component-base/index.js";
Installation via npm
Usable for projects with bundlers or using import maps.
npm i web-component-base
Usage
In your component class:
// HelloWorld.mjs
import WebComponent from "https://unpkg.com/web-component-base/index.js";
class HelloWorld extends WebComponent {
static properties = ["my-name", "emotion"];
get template() {
return `
<h1>Hello ${this.props.myName}${this.props.emotion === "sad" ? ". 😭" : "! 🙌"}</h1>`;
}
}
customElements.define('hello-world', HelloWorld);
In your HTML page:
<head>
<script type="module" src="HelloWorld.mjs"></script>
</head>
<body>
<hello-world my-name="Ayo" emotion="sad">
<script>
const helloWorld = document.querySelector('hello-world');
setTimeout(() => {
helloWorld.setAttribute('emotion', 'excited');
// or:
helloWorld.props.emotion = 'excited';
}, 2500)
</script>
</body>
The result is a reactive UI that updates on attribute changes:

template
vs render()
This mental model attempts to reduce the cognitive complexity of authoring components:
- The
template
is a read-only property (initialized with aget
keyword) that represents how the component view is rendered. - There is a
render()
method that triggers a view render. - This
render()
method is automatically called under the hood every time an attribute value changed. - You can optionally call this
render()
method at any point to trigger a render if you need.
Prop Access
The WebComponent.props
read-only property is provided for easy access to any observed attribute. This works like HTMLElement.dataset
, except dataset
is only for attributes prefixed with data-*
.
A camelCase counterpart using props
will give read/write access to any attribute, with or without the data-*
prefix.
Accessing props within the component
This API gives us read/write access to any attribute properties:
class HelloWorld extends WebComponent {
static properties = ["my-prop"];
onInit() {
let count = 0;
this.onclick = () => this.props.myProp = `${++count}`
}
get template() {
return `
<h1>Hello ${this.props.myProp}</h1>
`;
}
}
With this, the this.props
object gives us an easy way to read or write property values, which also triggers the attribute change hook.
The alternatives are using what HTMLElement
provides which are
HTMLElement.dataset
for attributes prefixed withdata-*
- Methods for reading/writing attribute values:
setAttribute(...)
andgetAttribute(...)
Updating props in the parent
Assigning value to the props.camelCase
counterpart will trigger an attribute change too.
For example, assigning a value like so:
this.props.myName = 'hello'
...is like calling the following:
this.setAttribute('my-name','hello');
Therefore, this will tell the browser that the UI needs a render if the attribute is one of the component's observed attributes we explicitly provide with static properties = ['my-name']
;
Quick Start Example
Here is an example of using a custom element in a single .html file:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>WC Base Test</title>
<script type="module">
import WebComponent from "https://unpkg.com/web-component-base/index.js";
class HelloWorld extends WebComponent {
static properties = ["my-name"];
get template() {
return `<h1>Hello ${this.props.myName ?? 'World'}!</h1>`;
}
}
customElements.define("hello-world", HelloWorld);
</script>
</head>
<body>
<hello-world my-name="Ayo"></hello-world>
<script>
const helloWorld = document.querySelector('hello-world');
setTimeout(() => {
helloWorld.props.myName = 'Ayo zzzZzzz';
}, 2500);
</script>
</body>
</html>
Life-Cycle Hooks
Define behavior when certain events in the component's life cycle is triggered by providing hook methods
onInit()
- Triggered when the component is connected to the DOM
- Best for setting up the component
import WebComponent from "https://unpkg.com/web-component-base/index.js";
class ClickableText extends WebComponent {
// gets called when the component is used in an HTML document
onInit() {
this.onclick = () => console.log(">>> click!");
}
get template() {
return `<span style="cursor:pointer">Click me!</span>`;
}
}
afterViewInit()
- Triggered after the view is first initialized
class ClickableText extends WebComponent {
// gets called when the component's innerHTML is first filled
afterViewInit() {
const footer = this.querySelector('footer');
// do stuff to footer after view is initialized
}
get template() {
return `<footer>Awesome site © 2023</footer>`;
}
}
onDestroy()
- Triggered when the component is disconnected from the DOM
- best for undoing any setup done in
onInit()
import WebComponent from "https://unpkg.com/web-component-base/index.js";
class ClickableText extends WebComponent {
clickCallback() {
console.log(">>> click!");
}
onInit() {
this.onclick = this.clickCallback;
}
onDestroy() {
console.log(">>> removing event listener");
this.removeEventListener("click", this.clickCallback);
}
get template() {
return `<span style="cursor:pointer">Click me!</span>`;
}
}
onChanges()
- Triggered when an attribute value changed
import WebComponent from "https://unpkg.com/web-component-base/index.js";
class ClickableText extends WebComponent {
// gets called when an attribute value changes
onChanges(changes) {
const {property, previousValue, currentValue} = changes;
console.log('>>> ', {property, previousValue, currentValue})
}
get template() {
return `<span style="cursor:pointer">Click me!</span>`;
}
}