Skip to content

WebComponent

Defined in: web-components-integration/src/WebComponent.ts:263

This is the base web components class and the whole web components implementation.

This is just a custom element with a number of necessary features missing from the standard custom elements.

This web components implementation was designed with the following goals in mind:

  1. Improve upon custom elements:
    1. Implement necessary features that are missing from the standard custom elements.
    2. Maintain compliance with the custom elements specification.
    3. Maintain compatibility with other libraries and frameworks without additional setup.
  2. Keep implementation minimal and size small:
    1. Don’t pollute application’s bundle with the code that most likely won’t be used.
    2. Don’t implement features for the sake of having features.
  3. Achieve maximum performance.

As you can see, this is not a framework. There are no quality-of-life abstractions and features found in major frameworks like Lit. Ultimately, this is a bare minimum to ship off web components integration.

When you are:

  • Occasionally using web components (or just vite-awesome-svg-loader alone).
  • Building a wrapper around vite-awesome-svg-loader.
  • Having other reasons to use a minimal web components implementation like this one.

When you are:

  • Planning or already using a dedicated library for managing web components.

    Just use that library.

  • Planning to build more web components.

    Consider using a dedicated library. It will pay off in the long run.

Behavioral differences from custom elements

Section titled “Behavioral differences from custom elements”
  1. All attributes are observed automatically. No need for WebComponent.observedAttributes.

  2. WebComponent.attributeChangedCallback is called when component has initialized and when any attribute’s value has actually changed. Setting same value doesn’t count as a change.

  3. Properties can be synchronized with attributes by defining them in WebComponent.props.

  1. Use constructors to initialize state. But do not use properties defined in WebComponent.props in a constructor because that will trigger an attribute setter which renders constructor invalid (per custom elements specification).

  2. Use WebComponent.connectedCallback to initialize markup.

  3. Use WebComponent.attributeChangedCallback to track property/attribute changes. Do not define your own getters and setters, they will be overridden.

  4. Prefer inheritance over composition. Yes, this is an anti-pattern. Web components are actual elements. The more components you nest, the larger and slower to process DOM becomes. If you want composition, either sacrifice performance or use a specialized library/framework.

  5. When using a web component inside another web component, call define({ noRedefinitionError: true }) and use a constructor instead of document.createElement().

    Suppress a redefinition error because in most cases user will not specify different tag and would not expect an error if they haven’t defined a dependency component.

    Use a constructor because user still might decide to use a custom tag.

    Ultimately, user will get an error, if the definition is invalid.

  6. If you’ve encountered a problem that must be resolved immediately by modifying the internals, everything’s here for you. Just use // @ts-ignore to suppress the errors. After the hotfix, please file an issue and explain your case.

Let’s create a simple BusinessCard web component:

class BusinessCard extends WebComponent implements CustomElement {
// Define class properties that will be synced with attributes
static readonly props = ["personName", { name: "orgName", default: "Not affiliated" }];
// Declare properties for type-checking to work. You may actually define them, but they will be overridden
// with proper getters and setters, so don't create empty overhead.
declare personName?: string;
declare orgName: string;
// Add markup
connectedCallback() {
super.connectedCallback();
if (this.children.length) {
return
}
const personName = document.createElement("div");
personName.className = "person-name";
this.appendChild(personName);
const orgName = document.createElement("div");
orgName.className = "org-name";
this.appendChild(orgName);
}
// Track changes
attributeChangedCallback(name: string, oldValue: string | null, newValue: string | null) {
switch (name) {
// props are tracked:
case "personName":
case "orgName":
this.getElementsByClassName(name)[0]!.innerText = newValue || "-";
break;
// Every other attribute is also tracked:
case "data-some-attr":
console.log(oldValue, newValue);
break;
}
}
// Provide default tag for definition (optional, but recommended)
define(options?: WebComponentDefinitionOptions) {
super.define({ ...options, tag: options.tag || "business-card" });
}
}
// Define a custom element
BusinessCard.define();

Let’s instantiate and mount a BusinessCard web component that we’ve created:

// Create a web component instance:
const card = new BusinessCard();
// or:
const card = document.createElement("business-card");
// Add component to the DOM:
document.getElementById("card")!.appendChild(card);
// Change properties:
card.firstName = "John";
// or:
card.setAttribute("first-name", "John"); // Notice that attribute is in snake-case while property is in camelCase

Let’s create a CoolBusinessCard component by extending BusinessCard component:

class CoolBusinessCard extends BusinessCard implements CustomElement {
// Define new properties. Inherited properties will be defined and copied to "props" automatically.
// Note: it is impossible to redefine inherited properties because parent relies on their behavior.
static readonly props = [
{ name: "coolness", default: "120%" },
];
declare coolness: string;
// Provide default tag for definition
define(options: WebComponentDefinitionOptions = {}) {
super.define({ ...options, tag: options.tag || "cool-business-card" });
}
}
  • HTMLElement
  • CustomElement

new WebComponent(): WebComponent

Defined in: web-components-integration/src/WebComponent.ts:320

WebComponent

HTMLElement.constructor

protected _state: WebComponentState = "uninitialized"

Defined in: web-components-integration/src/WebComponent.ts:284

Current component state. See WebComponentState for the details.


static optional observedAttributes: string[]

Defined in: web-components-integration/src/WebComponent.ts:270

Doesn’t do anything. All attributes are already observed. This property will be deleted when custom element will be defined.

This property is recognized for compatibility reasons only.


static props: (string | WebComponentProp)[] = []

Defined in: web-components-integration/src/WebComponent.ts:279

Properties that will be synced with attributes.

Properties must be in camelCase. Synchronized attributes will be in kebab-case.

To not confuse cases, prefer single-word names.

optional attributeChangedCallback(…args): void

Defined in: web-components-integration/src/WebComponent.ts:316

Called when:

  1. Component has just finished initialization. Then:

    1. _state property will be attrs-first-change.
    2. oldValue argument will be null.
    3. newValue will have a default value or null, if there’s no default.
    4. Thus, following may be true: oldValue === newValue && newValue === null.
  2. Any of the element’s attributes has actually changed (unlike default custom elements’ behavior where this method is called even if the same value has been set). Then:

    1. _state property will be normal.
    2. oldValue and newValue will be always different.

…[string, string | null, string | null]

void

CustomElement.attributeChangedCallback


connectedCallback(): void

Defined in: web-components-integration/src/WebComponent.ts:406

Called when the element is added to a document.

If overridden, super.connectedCallback() must be the first statement.

void

CustomElement.connectedCallback


getAttribute(name): string | null

Defined in: web-components-integration/src/WebComponent.ts:412

The getAttribute() method of the element.

MDN Reference

string

string | null

CustomElement.getAttribute

HTMLElement.getAttribute


removeAttribute(name): void

Defined in: web-components-integration/src/WebComponent.ts:423

The Element method removeAttribute() removes the attribute with the specified name from the element.

MDN Reference

string

void

CustomElement.removeAttribute

HTMLElement.removeAttribute


setAttribute(name, value): void

Defined in: web-components-integration/src/WebComponent.ts:419

The setAttribute() method of the Element interface sets the value of an attribute on the specified element.

MDN Reference

string

string

void

CustomElement.setAttribute

HTMLElement.setAttribute


toggleAttribute(name, force?): boolean

Defined in: web-components-integration/src/WebComponent.ts:427

The toggleAttribute() method of the present and adding it if it is not present) on the given element.

MDN Reference

string

boolean

boolean

CustomElement.toggleAttribute

HTMLElement.toggleAttribute


protected _ctor(): typeof WebComponent

Defined in: web-components-integration/src/WebComponent.ts:485

Returns a constructor of this class

If you need to refer to your own class, type it like so:

class MyClass extends WebComponent {
declare protected _ctor: () => typeof MyClass
}

typeof WebComponent

constructor of this class


protected _init(): void

Defined in: web-components-integration/src/WebComponent.ts:353

Initializes component state.

Custom elements may not add attributes or children in a constructor, so separate initialization is required. Thus, this method is called whenever an attribute is accessed or in a microtask after component’s constructor has run.

Initialization is performed only once.

void


static define(options): void

Defined in: web-components-integration/src/WebComponent.ts:508

Defines a custom element for this web component.

Won’t throw an error if called multiple times with the same tag.

Will throw an error (unless suppressed via BasicWebComponentDefinitionOptions.noRedefinitionError) if defined under a different tag.

BasicWebComponentDefinitionOptions

Definition options

void


protected static _initClass(): void

Defined in: web-components-integration/src/WebComponent.ts:527

Initializes class itself. Called by define and by the constructor. Initialization happens only once.

void