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>
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>
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>
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With