Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to set dynamic observedAttributes

My goal is to set observedAttributes dynamically, so my web component will watch only attributes following a pattern, like a colon (:attr) <my-element static="value1" :dynamic=${dynamic}/>
In this case, <my-element> should set observedAttributes only for the attribute :dynamic

The problem is that static get observedAttributes() runs before there's even a this, explained in https://andyogo.github.io/custom-element-reactions-diagram/

So this won't work

static get observedAttributes() {
   return this.getAttributeNames().filter((item) => item.startsWith(':'));
}

and of course neither does

constructor() {
        super();
        this._observedAttributes = this.getAttributeNames().filter((item) => item.startsWith(':'));
    }
    static get observedAttributes() {
        return this._observedAttributes;
    }

Thanks!

<!DOCTYPE html>

<body>
    <my-element static="value1" :dynamic="value2" ></my-element>
    <script>
        class MyElement extends HTMLElement {
            constructor() {
                super();
                this._observedAttributes= this.getAttributeNames().filter((item) => item.startsWith(':'));
                console.log('observedAttributes',this._observedAttributes);
            }
            static get observedAttributes() {
                return this._observedAttributes;
            }
            attributeChangedCallback(name, oldValue, newValue) {
                console.log(name, oldValue, newValue); //doesn't log anything
            }
        }
        customElements.define("my-element", MyElement);
        setTimeout(() => {
            console.log('setting dynamic attribute. This should trigger attributeChangedCallback. But no.');
            document.querySelector('my-element').setAttribute(':dynamic', 'value3');
        }, 2000);
    </script>
</body>

</html>
like image 474
daniel p Avatar asked Oct 21 '25 11:10

daniel p


2 Answers

You can use the following shim that will add the functionality of observing change of attributes dynamically on every instance of the element. You can define attributes that will be observed on all instances by default and you can add at the runtime attributes that will be observed only at a particular instance.

class BaseClass extends HTMLElement {
  constructor({stat = BaseClass} = {}) {
    super()
        // console.log('constructor')
        this.#stat = stat
        this.#setAttribute = this.setAttribute
        this.setAttribute = function(name, value){
            // console.log('attribute', name, this.observedAttributes, (this.observedAttributes.includes(name)))
            if (this.observedAttributes.includes(name)){
                // do your stuff
                // console.log('attribute includes')
                this.attributeChangedCallback(name, this.getAttribute(name), value)
            }
            else {
            // do what you want
            }
            this.#setAttribute(name, value)
        }
  }
    #observedAttributes = []
    get observedAttributes(){
        return [...this.#stat.observedAttributes, ...this.#observedAttributes]
    }
    #setAttribute = null
    #stat = null
    addObservedProp(name){
        this.#observedAttributes.push(name)
    }
  connectedCallback() {}
  disconnectedCallback() {}
  attributeChangedCallback(name, oldValue, newValue) {
        console.log('change', name, oldValue, newValue)
    }
  adoptedCallback() {}
  static get observedAttributes() {
    return ['a', 'b', 'c']
  }
}

window.customElements.define('base-com', BaseClass)

const test = document.getElementById('test')
test.setAttribute('d', 'status 1')
test.addObservedProp('d')
test.setAttribute('d', 'status 2')
console.log(test)
<base-com id="test">Base com</base-com>
like image 55
Mobeetle Avatar answered Oct 22 '25 23:10

Mobeetle


Following @Danny '365CSI' Engelman's suggestion, I looked at the MutationObserver API and came up with a simple solution that I think offers more than observedAttributes

<my-element static="value1" :dynamic="value2"></my-element>
<script>
  class MyElement extends HTMLElement {
    constructor() {
      super();
    }
    mutationObserverCallback(mutationList, observer) {
      for (const mutation of mutationList) {
        if (mutation.type === 'attributes' &&
          mutation.attributeName.startsWith(':') &&
          mutation.oldValue !== mutation.target.getAttribute(mutation.attributeName)) {
          console.log(`The dynamic ${mutation.attributeName} attribute was modified.`);
        }
      }
    }
    connectedCallback() {
      this.mutationObserver = new MutationObserver(this.mutationObserverCallback);
      this.mutationObserver.observe(this, {
        attributes: true,
        attributeOldValue: true
      });
    }
    disconnectedCallback() {
      this.mutationObserver.disconnect();
    }
  }
  customElements.define("my-element", MyElement);
  setTimeout(() => {
    console.log('setting dynamic attribute for :dynamic and static attributes. This should trigger mutationObserverCallback for :dynamic only.');
    // ▼ will trigger mutationObserverCallback
    document.querySelector('my-element').setAttribute(':dynamic', 'value3');
    // ▼ will not trigger mutationObserverCallback
    document.querySelector('my-element').setAttribute('static', 'value4');
  }, 200);
</script>
like image 41
daniel p Avatar answered Oct 22 '25 23:10

daniel p



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!