user103643
user103643

Reputation: 43

attributeChangedCallback doesn't get called even after defining observedAttributes

In the following code, attributeChangedCallback never gets called even if the 'content' attribute is created, changed or removed.

class Square extends HTMLElement {

    static get observedAttributes() {

        return ['content'];
    }
    constructor(val) {
        super();
        console.log('inside constructor');
        this.attachShadow({mode: 'open'});
        this.shadowRoot.appendChild(document.createElement('button'));

        this.button = this.shadowRoot.querySelector('button');
        this.button.className = "square";

        this.content = val;

        console.log('constructor ended');
    }

    get content() {
        console.log('inside getter');
        return this.button.getAttribute('content');
    }
    set content(val) {
        console.log('setter being executed, val being: ', val);
        // pass null to represent empty square
        if (val !== null) {
            this.button.setAttribute('content', val);

        } else {
            if (this.button.hasAttribute('content')) {
                this.button.removeAttribute('content');
            }
        }

    }
    connectedCallback() {
        //console.log('connected callback being executed now');
    }

    // not working :(
    attributeChangedCallback(name, oldValue, newValue) {
        console.log('attribute changed callback being executed now');
        if (name === 'content') {
            this.button.innerHTML = newValue?newValue:" ";
        }
    }
}
customElements.define('square-box', Square); 

Based on the best practices given here, I want the side-effect of attribute change (updation of innerHTML in my case) to take place in attributeChangedCallback. However when I move this updation to the setter, the code works fine.

Upvotes: 4

Views: 11917

Answers (1)

set content(val) {
        console.log('setter being executed, val being: ', val);
        // pass null to represent empty square
        if (val !== null) {
            this.button.setAttribute('content', val);

        } else {
            if (this.button.hasAttribute('content')) {
                this.button.removeAttribute('content');
            }
        }

    }

You are mixing up parents & children

You are defining a setter on your Element <square-box> (►parent element)

When you do this.button.setAttribute('content', val);

You are changing a attribute of your <button> Element (►child element)

This will never trigger the attributeChangedCallback of (►parent) <square-box>
because its attributes were not changed

You either have to go "up the DOM" with .getRootNode() and/or .host to set attributes of parent elements.

or use Custom Events (bubbling up the DOM) to notify parents that children have done/changed something

I presume you meant to do

set content(val) {
        //  loose ==null comparison for null AND undefined, 
        //  element.content=null; will remove the attribute
        if (val==null) 
            this.removeAttribute('content');
        else 
        //  but you DO want .content(0) (0==false) set as "0"
            this.setAttribute('content', val);
    }

Upvotes: 4

Related Questions