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.
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.
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():
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/disconnectedhooks with a flag prevent duplicate initialization. - Parsing asynchrony is fixed with defer/module or whenDefined.
— Editorial Team
No comments yet.