Search code examples
javascriptweb-component

Web Components: setter not being called


Say I have a Web Component:

customElements.define("custom-list", class CustomList extends HTMLElement {
  get filter() {
    console.log("get filter");
    return this.getAttribute("filter");
  }

  set filter(value) {
    console.log("set filter");
    this.setAttribute("filter", value);
  }
});

I wanted to use the setter method to do some initial attribute validation, but the setter never gets called. I tried setting the attribute through the HTML:

<custom-list filter="some value"></custom-list>

Only when I use JavaScript to set a property programmatically does the setter get called:

var list = document.querySelector("custom-list");
list.filter = "some value";

list.setAttribute("filter", "some value"); // DOESN'T WORK EITHER

So, it seems like setting attributes through the HTML or using setAttribute doesn't trigger the setter, which I partly can understand. My questions are:

  • Is the setter only necessary when I want to set properties programmatically?
  • How could I do initial validation of an attribute? In the connectedCallback? Say I want to only accept a certain string, how would I detect that?
  • Since the property filter gets populated anyway, do I need the setter if I don't use JavaScript to set my attributes?

Solution

    • Is the setter only necessary when I want to set properties programmatically?

    Yes, at least if you want/need to run some tests/filtering upon the value you want to set.

    • How could I do initial validation of an attribute? In the connectedCallback? Say I want to only accept a certain string, how would I detect that?

    Yep, connectedCallback or even in the constructor.

    • Since the property filter gets populated anyway, do I need the setter if I don't use JavaScript to set my attributes ?

    No, you don't

    This being said if you need a clear control over your custom attributes, i would suggest creating an internal state that you populate once when your custom element is being created and then when attributeChangedCallback is being called. That would give you some advantages :

    • you get control over the values that value your custom attributes.
    • you get an internal state that you can use to re-render your component if you need to

    Here is an example :

    customElements.define("custom-list", class CustomList extends HTMLElement {
    
        static get observedAttributes() { return ['filter']; }
    
        constructor() {
            super();
            this.state = {
                filter: null
            };
            this.setFilter(this.getAttribute("filter"));
        }
    
        attributeChangedCallback(name, oldValue, newValue) {
            if (name === "filter") {
                this.setFilter(newValue);
            }
        }
    
        getFilter() {
            console.log("get filter");
            return this.state.filter;
        }
    
        setFilter(value) {
            // You can add some logic here to control the value
            console.log("set filter");
            this.state.filter=value;
        }
    });
    

    Then you can call the following to change your internal state :

    list.setAttribute("filter", "some value");
    

    Would be interrested to get some feedback on this from the community. Anyway, hope this helps :)