LitElement: Two-way data binding
3 Patterns to migrate two-way data binding code from PolymerElement to LitElement
Background
Part of Polymer’s beauty was the simple two-way data binding mechanism. For example, the following snippet would update the name
field whenever the value of the input element changed:
<input value="{{name::input}}">
Yet, over the years, we learned that two-way data binding causes lots of headaches in complex applications. Popularized by Redux, unidirectional data flows became the standard. LitElement
, the replacement of PolymerElement
, therefore doesn’t provide any longer two-way data binding.
Still, applications need to access the values of their child elements. Here are the patterns that we use when migrating from PolymerElement
to LitElement
.
Pattern 1: Access value on button click
A surprisingly large portion of two-way data binding cases is getting data at a specific point of time, e.g. when the user clicks the [Save] button.
For those situation, we simply request the value from the element when it’s required:
@customElement('my-element')
export class MyElement extends LitElement {
protected render() {
return html`
<input id="name">
<button @click="${this.onSave}">Save</button>
`;
}
private onSave() {
console.log(this.inputEl.value);
}
private get inputEl(): HTMLInputElement {
return this.shadowRoot!.getElementById('name')! as HTMLInputElement;
}
}
Hint: Isolating the getElementById
call in a separated method allows proper typing (assuming you use TypeScript) and provides a centralized point to interact with the element, e.g. to call methods of this element.
Pattern 2: Access value on change
Sometimes a value should be saved when the user changed it, e.g. auto save a field when the user removes the focus from the input box.
For this, we trigger the above flow whenever the change
event is fired:
@customElement('my-element')
export class MyElement extends LitElement {
protected render() {
return html`
<input id="name" @change="${this.onChange}">
`;
}
private onChange() {
console.log(this.inputEl.value);
}
private get inputEl(): HTMLInputElement {
return this.shadowRoot!.getElementById('name')! as HTMLInputElement;
}
}
Pattern 3: Access value on keystroke
Finally, there are cases when we want to access the value whenever the user types another character, e.g. to enable the save button when a value was entered. For this, the input
event is our friend:
@customElement('my-element')
export class MyElement extends LitElement {
@property({type: Boolean})
private canSave: boolean = false;
protected render() {
return html`
<input id="name" @input="${this.onInput}">
<button disabled="${this.canSave}">Save</button>
`;
}
private onInput() {
this.canSave = this.inputEl.value.length > 0;
}
private get inputEl(): HTMLInputElement {
return this.shadowRoot!.getElementById('name')! as HTMLInputElement;
}
}
Happy coding!