Search code examples
htmlcustom-element

How can I pass initial state data to a web component?


I'm creating a custom element and looking for a good way to pass in an object representing its initial state. I'm thinking my html would look something like this:

<my-component id="myComponent1"></my-component>
<script>
    const myComponent1=document.getElementById("myComponent1");
    myComponent1.stateObj={ orientation:"inverted", splines:5, clowns:true }
</script>

And my js files will include:

class MyComponent extends HTMLElement {
    stateObj;

    constructor(){ super(); }

    connectedCallback(){
        // some init stuff
    }

    get stateObj(){
        return this.stateObj;
    }

    set stateObj(stateObj){
        this.stateObj = stateObj;
    }
}
customElements.define('my-component',MyComponent);

In testing this appears to work, and I like that the initial state is defined in my html document right next to the component. But can I be sure the component will always be ready to receive a property assignment from a script tag parsed immediately after it?


Solution

  • Like any HTML element, just be sure it exists in the DOM before you do something with the element.

    Some notes:

    • a constructor with only a super() is redundant, as the parent class constructor is called by default when it is doesn't exist

    • Your this.stateObj = stateObj overwrites the get stateObj()

    • defining the Web Component with an anonymous class is faster, shorter
      Unless you want to re-use a Web Component Class multiple times (99,9999% not required)

    • this._stateObj = obj passes a reference to an existing Object, you are not creating a new Object... its JavaScript!

    • this.stateObj returns undefined in the connectedCallback because it is not set yet

    <my-component id="myComponent1"></my-component>
    
    <script>
      customElements.define('my-component', class extends HTMLElement {
        connectedCallback() {
          console.log(this.id, "connected", this.stateObj)
        }
        get stateObj() {
          return this._stateObj;
        }
        set stateObj(obj) {
          console.log(obj);
          this.innerHTML = JSON.stringify(obj);
          this._stateObj = obj;
        }
      });
    </script>
    
    <script>
      const myComponent1 = document.getElementById("myComponent1");
      if (myComponent1)
        myComponent1.stateObj = {
          orientation: "inverted",
          splines: 5,
          clowns: true
        }
    </script>

    With a text attribute:

    • If you use an attribute to pass an Object, it needs to be a correctlty quoted String.
      https://javascript.info/json

    • The Web Component then converts the String to an Object

    • This a 30+ years old HTML limitation because attribute values are always a String

    • Note the difference between setting state (first example above) and loading state (second example below)
      No need to check if <my-component> exists in the DOM,
      this Web Component is 100% in control

    <script>
      customElements.define('my-component', class extends HTMLElement {
        connectedCallback() {
          this.stateObj = this.getAttribute("json");
          console.log(this.id,"connected",this._stateObj);
          this.innerHTML = JSON.stringify(this.stateObj)
        }
        get stateObj() {
          return this._stateObj || {}
        }
        set stateObj( obj ) {
          if( typeof obj == "string" ){
            this._stateObj = JSON.parse(obj)
          } else {
            this._stateObj = obj;
          }
        }
      });
    </script>
    
    <my-component id="load_with_attribute"
          json='{"orientation":"inverted", "splines":5, "clowns":true}'></my-component>

    Addition after comment

    <script>
      customElements.define('my-component', class extends HTMLElement {
        connectedCallback() {
          this.stateObj = {"orientation":"inverted", "splines":5, "clowns":true};
        }
        get stateObj() {
          return this.stateObj // !!!
        }
        set stateObj( obj ) {
          this.stateObj = obj; // !!!
        }
      });
    </script>
    
    <my-component></my-component>

    JSWC