Search code examples
javascriptweb-component

Can I set attributes and slot text at the same time as instantiating a custom element in JS


If I use a custom element in HTML the provided attributes are all available in the constructor

<my-element myparam="fruit">I like bananas</my-element>

constructor() {}
    super()
    this.param = this.getAttribute("myparam")

> this.param = "fruit"

But if I create an instance of my custom element in JS with document.createElement the constructor runs immediately, without the attributes being available.

const elem = document.createElement("my-element")

> this.param = null

Once I have appended the element to a DOM node I can hook attributeChangedCallback which fires when I call elem.setAttribute, to set this.param. But that seems kind of ugly. I feel as though you ought not to have to create extra methods just to make a component usable via JS as well as HTML. It seems like there's similar situation with textNodes / Children too.

Is there any way to make attributes and children available to the constructor when creating nodes programmatically?

edit: So it seems seeking to do this is bad practice and I should only try to access attributes in the connectedCallback() function. As long as I use setAttribute before adding the component with .appendChild the values should be available a that point.


Solution

  • The constructor runs before the element has attached to the DOM when you use document.createElement so you will not be able to access attributes as they don't exist yet.

    You can access attributes in connectedCallback. The following basic example works

    customElements.define(
      "my-el",
      class extends HTMLElement {
        constructor() {
          super();
        }
        
        connectedCallback() {
          this.foo = this.getAttribute("foo")
          console.log("foo", this.foo) // logs "bar"
        }
      }
    );
    
    const el = document.createElement("my-el");
    el.setAttribute("foo", "bar");
    document.body.append(el)
    

    You have some typos: my-element vs. my-component, this.getAttribute("myparam") when you attribute above is called param.