Start · Sprachen · JavaScript · Referenz · CustomElementRegistry

CustomElementRegistry

Schnittstelle

Registriert benutzerdefinierte Elemente (Custom Elements) und fragt registrierte Elemente ab. Die globale Instanz erreicht man über <code>window.customElements</code>.

seit JavaScript Baseline: alle modernen Browser (seit 2020); Custo Kategorie: webapi

Signatur

interface CustomElementRegistry

Beschreibung

CustomElementRegistry ist die zentrale Registrierungsstelle für Custom Elements, einen Teil der Web-Components-Technik. Du erzeugst keine eigene Instanz. Die globale Registry erreichst du über window.customElements. Dort verknüpfst du einen Tag-Namen wie <user-card> mit einer JavaScript-Klasse, die von HTMLElement oder einem eingebauten Element erbt.

Die wichtigsten Methoden:

  • define(name, constructor, options): registriert ein neues Element. Bereits vorhandene Elemente im DOM werden sofort „aufgewertet“ (Upgrade).
  • get(name): gibt den Konstruktor eines registrierten Elements zurück, sonst undefined.
  • getName(constructor): gibt den registrierten Namen zu einer Klasse zurück, sonst null (ab 2024 verfügbar).
  • whenDefined(name): liefert ein Promise, das erfüllt wird, sobald das Element definiert ist. Das ist nützlich bei Lazy Loading und um das Aufblitzen noch nicht gestylter Inhalte (FOUC) zu vermeiden.
  • upgrade(root): wertet Custom Elements in einem Teilbaum auf, der noch nicht mit dem Dokument verbunden ist.

Du brauchst die Registry immer dann, wenn du wiederverwendbare, framework-unabhängige Komponenten baust, Komponenten erst bei Bedarf nachlädst oder vor einem Zugriff prüfen musst, ob eine Komponente schon verfügbar ist.

Parameter

Name Typ Default Beschreibung
$name Pflicht string Parameter von define(), get() und whenDefined(): der Tag-Name. Er muss einen Bindestrich enthalten, mit einem Kleinbuchstaben beginnen und darf keine Großbuchstaben enthalten, z. B. my-button.
$constructor Pflicht CustomElementConstructor Parameter von define() und getName(): eine Klasse, die von HTMLElement erbt (bei eingebauten Elementen z. B. von HTMLButtonElement).
$options { extends?: string } undefined Optionaler Parameter von define(). extends gibt das eingebaute Element an, das erweitert wird (Customized Built-in Element), z. B. { extends: 'button' }.
$root Pflicht Node Parameter von upgrade(): der Wurzelknoten, dessen Nachfahren aufgewertet werden sollen.

Rückgabewert

Typ
CustomElementRegistry
Beschreibung
Über window.customElements erreichbar. Rückgabewerte der Methoden: define() und upgrade() liefern undefined, get() liefert CustomElementConstructor | undefined, getName() liefert string | null und whenDefined() liefert Promise<CustomElementConstructor>.

Beispiele

Eigenes Element definieren und verwenden

class UserCard extends HTMLElement {
  static observedAttributes = ['name'];

  connectedCallback() {
    this.render();
  }

  attributeChangedCallback() {
    this.render();
  }

  render() {
    this.textContent = `Hallo, ${this.getAttribute('name') ?? 'Gast'}!`;
  }
}

// Doppelte Registrierung vermeiden
if (!customElements.get('user-card')) {
  customElements.define('user-card', UserCard);
}

const card = document.createElement('user-card');
card.setAttribute('name', 'Anna');
document.body.append(card);
console.log(card.textContent);
console.log(customElements.getName(UserCard));
Hallo, Anna! user-card

Mit whenDefined() auf eine nachgeladene Komponente warten

// Komponente erst bei Bedarf laden
const loadDatePicker = () => import('./date-picker.js'); // ruft darin customElements.define('date-picker', …) auf

loadDatePicker();

const DatePicker = await customElements.whenDefined('date-picker');
console.log('Bereit:', DatePicker.name);

// Alle noch nicht definierten Elemente abwarten und erst dann einblenden
const undefinedTags = [...document.querySelectorAll(':not(:defined)')]
  .map((el) => el.localName);
await Promise.all([...new Set(undefinedTags)].map((tag) => customElements.whenDefined(tag)));
document.body.classList.add('ready');
Bereit: DatePicker

Eingebautes Element erweitern und upgrade() nutzen

class ConfirmButton extends HTMLButtonElement {
  connectedCallback() {
    this.addEventListener('click', (e) => {
      if (!confirm('Wirklich ausführen?')) e.preventDefault();
    });
  }
}

customElements.define('confirm-button', ConfirmButton, { extends: 'button' });

// Teilbaum außerhalb des Dokuments erzeugen
const template = document.createElement('template');
template.innerHTML = '<button is="confirm-button">Löschen</button>';
const fragment = template.content.cloneNode(true);
const btn = fragment.querySelector('button');

console.log(btn instanceof ConfirmButton); // noch nicht aufgewertet
customElements.upgrade(fragment);
console.log(btn instanceof ConfirmButton);
false true

// Wichtig · Fallstricke

  • Doppelte Registrierung: Wird define() zweimal mit demselben Namen oder derselben Klasse aufgerufen, wirft es eine NotSupportedError-DOMException. Prüfe das vorher mit customElements.get(), besonders bei Hot Module Replacement oder mehrfach eingebundenen Skripten.
  • Ungültige Namen ohne Bindestrich oder mit Großbuchstaben (z. B. myelement, My-El) führen zu einem SyntaxError. Reservierte Namen wie font-face sind ebenfalls verboten.
  • Konstruktor-Regeln: Im constructor zuerst super() aufrufen. Dort keine Attribute oder Kindelemente anlegen oder lesen. DOM-Arbeit gehört in connectedCallback.
  • Customized Built-ins ({ extends: 'button' } mit dem Attribut is=\"…\") unterstützt Safari/WebKit nicht. Für browserübergreifenden Code besser autonome Elemente verwenden oder ein Polyfill einsetzen.
  • getName() ist neu (Baseline 2024). In älteren Browsern vorher mit 'getName' in customElements prüfen.
  • Namen gelten global pro Registry. Zwei Bibliotheken mit demselben Tag-Namen kollidieren. Nutze deshalb Präfixe (z. B. acme-button). Eigene, begrenzte Registries (Scoped Custom Element Registries) befinden sich noch in der Standardisierung.
  • Die Pseudoklasse :not(:defined) in CSS verhindert das Aufblitzen noch nicht definierter Elemente.