Search code examples
javascriptnestedattributescustom-element

custom elements not setting/getting attributes


if i create two custom elements and create one of them inside the other one, the attributes are not working on the new childs of the first element.

class Bar extends HTMLElement {
  constructor() {
    super();
    const val = this.getAttribute('val') || 'no value';

    const shadow = this.attachShadow({mode: 'open'});
    const wrapper = document.createElement('div');
    wrapper.innerHTML = `
      <div class='bar'>
      	<span>${val}</span>
      </div>
    `;

    shadow.appendChild(wrapper);
  }
}
customElements.define('x-bar', Bar);

class Foo extends HTMLElement {
  constructor() {
    super();
    const loop = this.getAttribute('loop') || 10;

    const shadow = this.attachShadow({mode: 'open'});
    const wrapper = document.createElement('div');
    
    for(let i=0; i<loop; i++){
    	const b = document.createElement('x-bar');
        b.setAttribute('val', `value #${i}`);
      
        wrapper.appendChild(b);
    }

    shadow.appendChild(wrapper);
  }
}
customElements.define('x-foo', Foo);
<x-foo loop='3'></x-foo>

im expecting that my output would be

value #0

value #1

value #2

as i have set the attr like this b.setAttribute('val', value #${i});

but im getting 3x no value

Any inputs on why that is? and/or how to fix it, Thanks!


Solution

  • You're not setting the attribute until after the constructor has already been called; note the logging:

    class Bar extends HTMLElement {
      constructor() {
        super();
        const val = this.getAttribute('val') || 'no value';
        console.log("In constructor, val = " + val);
    
        const shadow = this.attachShadow({mode: 'open'});
        const wrapper = document.createElement('div');
        wrapper.innerHTML = `
          <div class='bar'>
          	<span>${val}</span>
          </div>
        `;
    
        shadow.appendChild(wrapper);
      }
    }
    customElements.define('x-bar', Bar);
    
    class Foo extends HTMLElement {
      constructor() {
        super();
        const loop = this.getAttribute('loop') || 10;
    
        const shadow = this.attachShadow({mode: 'open'});
        const wrapper = document.createElement('div');
        
        for(let i=0; i<loop; i++){
          console.log("Constructing...");
        	const b = document.createElement('x-bar');
          console.log("Setting attribute");
            b.setAttribute('val', `value #${i}`);
          
            wrapper.appendChild(b);
        }
    
        shadow.appendChild(wrapper);
      }
    }
    customElements.define('x-foo', Foo);
    <x-foo loop='3'></x-foo>

    You'll need to move your rendering logic out of the constructor so you can take attributes set post-construction into account. Perhaps by overriding setAttribute:

    class Bar extends HTMLElement {
      constructor() {
        super();
    
        const shadow = this.attachShadow({mode: 'open'});
        this.wrapper = document.createElement('div');
    
        this.render(); // Though calling methods from the constructor isn't ideal
        shadow.appendChild(this.wrapper);
      }
      
      setAttribute(name, value) {
        super.setAttribute(name, value);
        if (name === "val") {
          this.render();
        }
      }
      
      render() {
        const val = this.getAttribute('val') || 'no value';
        this.wrapper.innerHTML = `
          <div class='bar'>
          	<span>${val}</span>
          </div>
        `;
      }
    }
    customElements.define('x-bar', Bar);
    
    class Foo extends HTMLElement {
      constructor() {
        super();
        const loop = this.getAttribute('loop') || 10;
    
        const shadow = this.attachShadow({mode: 'open'});
        const wrapper = document.createElement('div');
        
        for(let i=0; i<loop; i++){
          console.log("Constructing...");
        	const b = document.createElement('x-bar');
          console.log("Setting attribute");
            b.setAttribute('val', `value #${i}`);
          
            wrapper.appendChild(b);
        }
    
        shadow.appendChild(wrapper);
      }
    }
    customElements.define('x-foo', Foo);
    <x-foo loop='3'></x-foo>

    Calling methods from the constructor isn't ideal, though, you may want to fiddle with that a bit