0

WebUI: Add a documentation on -changed event handlers in Lit

The documentation already covered how 2 way bindings can be converted
from Polymer, but without the Polymer context it is not obvious to CL
authors that -changed events differ from events like click, keydown
etc in that they should not perform updates to the DOM, since the
element may not be done updating yet. Document that such handlers
may update properties on the parent but generally should not update the
DOM directly, instead deferring such updates to the updated() lifecycle
callback.

Bug: 40943652
Change-Id: I1b061dc5ac80bfea7d9d22b5c8506195c60bdaf9
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5725763
Commit-Queue: Rebekah Potter <rbpotter@chromium.org>
Reviewed-by: Demetrios Papadopoulos <dpapad@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1330579}
This commit is contained in:
rbpotter
2024-07-19 23:24:32 +00:00
committed by Chromium LUCI CQ
parent 93fa4397a5
commit 1524baaf37

@ -63,6 +63,79 @@ Specific features of `CrLitElement` include:
in Polymer - i.e. property `fooBar` will be mapped to attribute `foo-bar`,
not the Lit default of `foobar`
## Lit Data Bindings and handling `-changed` events
As noted above, `CrLitElement` forces an initial synchronous render in
`connectedCallback()`. This means child elements may initialize properties
with `notify: true` and then fire `-changed` events for these properties in
`updated()` as soon as they are connected, which may occur before the parent
element has finished its first update.
One consequence of this is that `-changed` event handlers cannot assume that
the element has completed its first update when the `-changed` event is
received, and should not make any changes to the element's DOM until after
waiting for the element's `updateComplete` promise. This means such handlers
must either (1) be async and `await this.updateComplete;` before running any
code that updates the element's DOM, or (2) only update properties on the
parent in response to the child's property change, and perform resulting UI
updates in the `updated()` lifecycle method instead.
Note that if the parent property being updated is protected or private, a cast
will be necessary to check for changes to the property in `changedProperties`.
This is also demonstrated in the example below.
Suppose the Lit child has a property with `notify: true` as follows:
```
static override get properties() {
return {
foo: {
type: Boolean,
notify: true,
},
};
}
```
This property is also bound to a parent element that listens for the
`-changed` event as follows:
```
<foo-child ?foo="${this.foo_}" on-foo-changed="${this.onFooChanged_}">
</foo-child>
<demo-child id="demo"></demo-child>
```
The parent TypeScript code could look like this:
```
static override get properties() {
return {
foo_: {type: Boolean},
};
}
protected foo_: boolean = true;
onFooChanged_(e: CustomEvent<{value: boolean}>) {
// Updates the parent's property that is bound to the child.
this.foo_ = e.detail.value;
}
override updated(changedProperties: PropertyValues<this>) {
super.updated(changedProperties);
// Cast necessary to check for changes to protected/private properties.
const changedPrivateProperties =
changedProperties as Map<PropertyKey, unknown>;
// Updates the DOM when |foo_| changes.
if (changedPrivateProperties.has('foo_')) {
if (this.foo_) {
this.$.demo.show();
} else {
this.$.demo.hide();
}
}
}
```
## Lit and Polymer Data Bindings Compatibility
Two-way bindings are not natively supported in Lit. As mentioned above,
basic compatibility is provided by the `CrLitElement` base classs