Maîtrisez le cycle de vie des custom elements, l'encapsulation du shadow DOM et la projection de contenu par slots.
Ouvrir cette leçon dans KodokonLes custom elements étendent le vocabulaire HTML : toute classe héritant de HTMLElement peut être enregistrée dans le CustomElementRegistry, à condition que son nom contienne un tiret (user-badge, jamais userbadge). Point subtil : si la balise apparaît dans le document avant l'appel à customElements.define, elle est d'abord créée comme élément inconnu puis upgradée sur place - le constructeur est alors invoqué a posteriori sur un nœud déjà présent dans l'arbre.
class UserBadge extends HTMLElement {
static observedAttributes = ["name"];
connectedCallback() {
this.render();
}
attributeChangedCallback() {
this.render();
}
render() {
this.textContent = this.getAttribute("name");
}
}
customElements.define("user-badge", UserBadge);Le cycle de vie réserve des pièges : attributeChangedCallback n'est déclenché que pour les attributs listés dans observedAttributes, et lors d'un upgrade il est appelé pour chaque attribut déjà présent avant connectedCallback. La spécification interdit au constructeur d'examiner les attributs et les enfants, ou d'en ajouter : au moment où il s'exécute (via createElement ou un clonage), l'élément peut être totalement vide.
<template id="card-tpl">
<style>
:host { display: block; padding: 1rem; }
::slotted(h2) { margin: 0; }
</style>
<slot name="title"></slot>
<slot></slot>
</template>attachShadow crée un sous-arbre encapsulé : les styles n'en sortent pas, les sélecteurs de la page n'y entrent pas - seules les propriétés CSS héritées (couleur, police) et les custom properties traversent la frontière. Le contenu d'un <template> vit dans un DocumentFragment accessible via .content : ses scripts ne s'exécutent pas et ses images ne se chargent pas tant que vous ne le clonez pas dans le document. Les nœuds placés dans le light DOM sont projetés dans les <slot> : ils restent physiquement dans le light DOM, et l'événement slotchange vous signale tout changement d'affectation.
class InfoCard extends HTMLElement {
constructor() {
super();
const tpl = document.getElementById("card-tpl");
this.attachShadow({ mode: "open" })
.appendChild(tpl.content.cloneNode(true));
}
}
customElements.define("info-card", InfoCard);Deux extensions méritent votre attention. Les form-associated custom elements : avec static formAssociated = true et attachInternals(), votre composant participe pleinement aux formulaires (valeur soumise, validation native, états :invalid). Et le shadow DOM déclaratif : <template shadowrootmode=open> attache un shadow root dès le parsing, sans JavaScript, ce qui rend les web components compatibles avec le rendu côté serveur.
attachShadow({ mode: 'closed' }), que renvoie element.shadowRoot depuis l'extérieur ?null : seule la référence retournée par attachShadow permet d'accéder au shadow root<slot> ?