Back to Home

Web Components: myths and truth about Custom Elements

The article debunks myths about web components: property passing, Shadow DOM, hooks. Provides code solutions and highlights strengths for complex dashboards and widgets.

Debunking myths about web components for developers
Advertisement 728x90

Web Components: Debunking Myths and Exploring Real Capabilities

Web components are often criticized for supposedly only allowing string property passing via attributes. This misconception stems from a misunderstanding of the attributeChangedCallback mechanism. HTML attributes are a static part of markup, not directly linked to the JS runtime. To pass objects, arrays, or primitives, use getters/setters in the component class. Browsers allow setting properties of any type directly through the DOM API or proxies.

class MyElement extends HTMLElement {
  set data(value) {
    this._data = value;
    this.render();
  }
}

Shadow DOM provides style and DOM isolation, protecting against external conflicts. Criticizing isolation as a flaw ignores the choice: Custom Elements work without Shadow DOM. For customization, use CSS variables and ::part():

my-component::part(button) {
  background: var(--accent-color);
}

The myth of style duplication: Adopted Style Sheets allow applying shared styles to all Shadow DOM instances.

Google AdInline article slot

The lifecycle begins in the constructor, not when connected to the DOM. Components are created in memory and manipulated via DocumentFragment for virtualization without rendering.

Half-Truth: Registry and Lifecycle Hooks

The global Custom Elements registry requires unique names. The solution is prefixes (e.g., app-button) and try/catch during registration:

try {
  customElements.define('app-button', ButtonElement);
} catch (e) {
  console.warn('Name conflict:', e);
}

Collisions are caught by tests. Flat namespaces are a standard pattern in JS, DNS, and HTML.

Google AdInline article slot

The connectedCallback/disconnectedCallback hooks trigger when moving in the DOM. The fix is an initialization flag:

connectedCallback() {
  if (this._initialized) return;
  this._initialized = true;
  this.init();
}

Deregistration is impossible, but this is a plus: browsers cache the constructor without overhead.

Real Nuances and Solutions

HTML parsing triggers connectedCallback on the opening tag before processing content. DOM APIs are unavailable. Use defer or type="module" for deferred initialization. An alternative is customElements.whenDefined():

Google AdInline article slot
const button = document.querySelector('app-button');
await customElements.whenDefined('app-button');
button.method();

To guarantee access to child elements, use renderCallback or similar hooks.

Strengths of Web Components

Web components natively link HTML markup with JS. Custom tags serve as markers for integration on the server or client without vendor lock-in.

Atomic lifecycle management: browsers notify about connection/disconnection independently of frameworks.

A foundational model for Lit, Symbiote.js, and micro-frontends. Works with React, Vue, Angular, SPAs, and SSR.

  • Isolation without overhead: Shadow DOM is optional.
  • Native reactivity: properties + proxies.
  • Versatility: from widgets to dashboards with WebSocket and graphics.
  • Virtualization: DocumentFragment for lists of 100k+ elements.
  • Styling: CSS vars, ::part(), Adopted Style Sheets.

Key Takeaways

  • Properties of any type are passed via getters/setters; attributes are only for strings.
  • Shadow DOM isolates but can be disabled if needed; style via vars and ::part.
  • The global registry is resolved with prefixes and tests; collisions are rare.
  • The connected/disconnected hooks with a flag prevent duplicate initialization.
  • Parsing asynchrony is fixed with defer/module or whenDefined.

— Editorial Team

Advertisement 728x90

Read Next