最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - Web Components: setter not being called - Stack Overflow

programmeradmin2浏览0评论

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?

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?
Share Improve this question asked Jun 11, 2019 at 15:26 Andreas RemdtAndreas Remdt 6551 gold badge9 silver badges16 bronze badges
Add a ment  | 

2 Answers 2

Reset to default 4

getters and setters are a way to define properties that allow your code to receive and return values other than strings.

Attributes on the element are always string values. You can emulate a non-string in an attribute by parsing the value. But they are always set in as strings and read back as strings.

If you want to have code run when an attribute is changed then you need to add the attributeChangedCallback function and indicate which attributes you are watching in the observedAttributes static getter.

Attributes can be set in JavaScript by calling setAttribute and removeAttribute. They are also set when the browser parses your HTML due to a page load or setting innerHTML. But even then the browser does not set these until after the constructor is called when the browser, eventually, calls setAttribute in the background.

customElements.define("custom-list", class CustomList extends HTMLElement {
  static get observedAttributes() { return ['filter']; }

  constructor() {
    super();
    this._filter = null; // Save the initial value
  }

  attributeChangedCallback(attr, oldVal, newVal) {
    if (oldVal != newVal) {
      // Only set this value if it is different
      this.filter = newVal; // Call the setter
    }
  }

  get filter() {
    console.log("get filter");
    return this._filter; // Return the internal value
  }

  set filter(value) {
    // Only run this if the new value is different from the internal value
    if (value !== this._filter) {
      console.log(`set filter ${value}`);
      this._filter = value; // Set the internal value
      this.textContent = value;
      // If you want the filter property to always show
      // in the attributes then do this:
      if (value !== null) {
        this.setAttribute('filter', value);
      } else {
        this.removeAttribute('filter');
      }
    }
  }
});

const el = document.querySelector('custom-list');
setTimeout(() => {
  el.filter = 'happy';
}, 2000);
<custom-list filter="10"></custom-list>

Always check to see if your oldVal and newVal are different in the function attributeChangedCallback. Otherwise you could end up in an endless loop.

It is also remended that you check for different values in your setters. Again, to avoid getting into an endless loop.

Setters also allow you to take specific data types. For example you could check to see if the value for value was a number and, if not, throw a TypeError. Or you just convert the ining value to the correct type.

Setters also allow you to make sure a value is valid. Maybe it must be a positive number or one of three possible strings. If it isn't you can throw a RangeError.

But you have to remember that attributes are always strings and properties can be anything.

  • 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 ponent 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 munity. Anyway, hope this helps :)

发布评论

评论列表(0)

  1. 暂无评论