admin管理员组

文章数量:1403443

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>

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!

Share Improve this question asked May 24, 2018 at 11:52 kejakeja 1,3631 gold badge15 silver badges21 bronze badges 1
  • Possible duplicate of Cannot access attributes of a custom element from its constructor – Supersharp Commented May 24, 2018 at 21:58
Add a ment  | 

2 Answers 2

Reset to default 6

Most people are unaware of the rules of a constructor for Web Components:

Here are the official docs:

https://w3c.github.io/webponents/spec/custom/#custom-element-conformance

The summary is:

Your constructor code:

  • must have a parameter-less call to super() as first statement in constructor.
  • must not have a return statement anywhere in constructor.
  • must not call document.write() or document.open().
  • must not inspect element's attributes.
  • must not change or add any attributes or children.

In general, the constructor should be used to set up initial state and default values, and to set up event listeners and possibly a shadow root.

In general I agree with @T.J. Crowder, but I would make one minor modification to the Bar object:

class Bar extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({mode: 'open'});
  }
  
  static get observedAttributes() {
    // Indicate that we want to be notified
    // when the `val` attribute is changed
    return ['val'];
  }
  
  connectedCallback() {
    // Render the initial value
    // when this element is placed into the DOM
    render(this.getAttribute('val'));
  }
  
  attributeChangedCallback(attrName, oldVal, newVal) {
    if (oldVal != newVal) {
      // If the value for the `val` attribute has changed
      // then re-render this element
      render(newVal);
    }
  }  
  
  render(val = 'no value') {
    this.shadowRoot.innerHTML = `
      <div class='bar'>
      	<span>${val}</span>
      </div>
    `;
  }
}

customElements.define('x-bar', Bar);

This used the standard of attributeChangedCallback and observedAttributes. While overriding the setAttribute functionality works it is not future proof. If the API for setAttribute were to change, in the future, then you would need to remember to fix your ponent. And doing this with many ponents racks up a lot of developer debt.

class Bar extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({mode: 'open'});
    // Render the blank DOM
    this.shadowRoot.innerHTML = '<div class="bar"><span>no value</span><div>';
    this._span = this.shadowRoot.querySelector('span');
  }
  
  static get observedAttributes() {
    // Indicate that we want to be notified
    // when the `val` attribute is changed
    return ['val'];
  }
  
  attributeChangedCallback(attrName, oldVal, newVal) {
    if (oldVal != newVal) {
      // If the value for the `val` attribute has changed
      // then insert the value into the `<span>`
      this._span.textContent = newVal || 'no value';
      // OR: this._span.innerHTML = newVal || 'no value';
      // But make sure someone has not tried to hit you
      // with a script attack.
    }
  }
  
  get val() {
    return this._span.textContent;
  }
  set val(newVal) {
    if (newVal == null || newVal === false || newVal === '') {
      this.removeAttribute('val');
    }
    else {
      this.setAttribute('val', newVal);
    }
  }
}

customElements.define('x-bar', Bar);

This second method done not re-render the entire DOM it simple inserts the modified attribute value into the <span> tag.

It also provides properties so you can set the value through the attribute as well as through JavaScript:

var el = document.querySelector('x-bar');
if (el) {
  el.val = "A New String";
  setTimeout(()=>el.val = '';,2000);
}

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

本文标签: javascriptcustom elements not settinggetting attributesStack Overflow