Search code examples
javascriptweb-componentnative-web-component

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>


Solution

  • 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>