Search code examples
javascripthtmldomgetattribute

Getting a custom element's id in its constructor


I'm trying to make somewhat of a reusable header for a website (multiple pages, can't be bothered to copy/paste everything); inside it I only have a logo-element element (custom as well) whose id I want to make equal to the header's plus a __logo suffix.

Here's the javascript:

class TopBar extends HTMLElement {
    constructor() {
        super();

        this.attachShadow({ mode: "open" });

        let logoElement = document.createElement("logo-element");
        logoElement.setAttribute("id", `${ this.getAttribute("id") }__logo`);

        this.shadowRoot.append(logoElement); 
    }
}

customElements.define('top-bar-element', TopBar);

And here's the HTML:

<top-bar-element id="top-bar"></top-bar-element>

Yet, when I check, I find <logo-element id="null__logo"></logo-element>.

I suppose it is because the browser only sets the attribute after the creation of the element.

Is there any other explanation? Are there workarounds?


Solution

  • You are correct, in the constructor phase the element is only in memory, can't access the DOM

    So you have to do it in the connectedCallback:

    customElements.define('my-element', class extends HTMLElement {
      constructor() {
        let content = ["slot","logo-element"].map(x=>document.createElement(x));
        super() // docs are wrong, super doesn't have to be first
          .attachShadow({mode: "open" })
          .append( ...content );
        this.id = "TWO";
      }
      connectedCallback() {
        this  
          .shadowRoot
          .querySelector("logo-element")
          .id = this.id + "__logo";
    
        console.log(this.shadowRoot.innerHTML);
      }
    });
    <my-element id="ONE">Hello World!</my-element>

    No constructor

    But you do not need the constructor in your own element (the default one from HTMLElement will be executed)

    customElements.define('my-element', class extends HTMLElement {
      connectedCallback() {
        this.attachShadow({mode: "open"}).innerHTML = `<slot></slot>`;
        let el = this.shadowRoot.appendChild(document.createElement("logo-element"));
        el.id = this.id + "__logo";
        console.log(this.shadowRoot.innerHTML);
    
        let host = this.shadowRoot.getRootNode().host;
        console.log(host==this , host); 
      }
    });
    <my-element id="TWO">Hello World!</my-element>

    Notes:

    • showing different uses to set element HTML content, you can mix what works for you
    • id is a default attribute (like title and name) and (by default) have a Getter/Setter
    • your logo-element works, but still is an UNKNOWN Element
    • Docs should say: You can not use the 'this' scope reference before super() is called
    • shadowDOM is not required; you can create a Custom Element without

    Without shadowDOM

    customElements.define('my-element', class extends HTMLElement {
      connectedCallback() {
        this.innerHTML = `<logo-element id="${this.id}__logo">${this.innerHTML}</logo-element>`;
        console.log(this.innerHTML);
      }
    });
    <my-element id="THREE">Hello World!</my-element>