Search code examples
javascriptweb-componentlit-element

OnChange not fired in input field of litElement


I have following code:

export class ViTextfield extends LitElement 
{
static get properties() {
    return { 
        value: { type: String }, 
  }

 onChange(e) { console.log(e.target.value) }

 render()
{
    return html`
    <div>

        <div>
            <input id="vi-input" 
                type="text" 
                value="${this.value}"
                @change=${this.onChange} />
        </div>
    </div>
        `
}

So everything is working fine for itself. Now the developer who is using my component should be able to set the value thorugh the property e.g.

  document.getElementById('myComponent').value = 1;

Now that brings 2 problems: 1) the value itself is not updated and 2) the onchange is not fired

Problem 1 I fixed with changing

value="${this.value}"

to

.value="${this.value}"

even I dont know why it is working (found this hack online).

But still the onChange is not firing...


Solution

  • The code doesn't work as you expect it to due to a couple of things:

    1. Why does value not work when .value does?

    lit-html uses the dot here to distinguish between assigning the value attribute or the property (value assigns the attribute and .value the property)

    The easiest way of thinking about this is that attributes are those set on the HTML itself and properties are set to the Javascript object that represents that node.

    Now, this is important in this case because the value property of an input element is only set from the attribute the when it's first rendered, if you want to change it later on you must set the property, not the attribute. Source

    1. Why isn't the change event fired when the value property is changed from code?

    This is because the change event is fired from the input only when the input's value changed due to some user input. Source

    If you want to have some sort of side effect that gets fired not only when the user interacts when the input, but also when the property is modified in code, you probably want to use a setter. In your case that would look like this:

    export class ViTextfield extends LitElement {
      static get properties() {
        return {
          value: {
            type: String
          },
        }
      }
    
      set value(value) {
        const oldValue = this.value;
        // do some side effect here        
        // set a pseudo-private property that will contain the actual value
        this._value = value;
        // call LitElement's requestUpdate so that a rerender is done if needed
        this.requestUpdate('value', oldValue);
      }
    
      get value() {
        // return the pseudo-private so that when vitextfield.value is accessed the correct value is returned
        return this._value;
      }
    
      onChange(e) {
        // update the property so that it keeps up with the input's current value
        this.value = e.target.value;
      }
    
      render() {
        return html `
        <div>
            <div>
                <input id="vi-input" 
                    type="text" 
                    value="${this.value}"
                    @change=${this.onChange} />
            </div>
        </div>
            `
      }
    }

    For more info check this part of the LitElement guide