diff --git a/CHANGELOG.md b/CHANGELOG.md index f9ad5ee5..7465630e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). * The value returned by `render` is always rendered, even if it isn't a `TemplateResult`. ([#712](https://github.com/Polymer/lit-element/issues/712) ### Added +* Added `@queryAsync(selector)` decorator which returns a Promise that resolves to the result of querying for the given selector after the element's `updateComplete` Promise resolves ([#903](https://github.com/Polymer/lit-element/issues/903)). * Added `enableUpdating()` to `UpdatingElement` to enable customizing when updating is enabled [#860](https://github.com/Polymer/lit-element/pull/860). * Added `queryAssignedNodes(slotName, flatten)` to enable querying assignedNodes for a given slot [#860](https://github.com/Polymer/lit-element/pull/860). * Added `getStyles()` to `LitElement` to allow hooks into style gathering for component sets [#866](https://github.com/Polymer/lit-element/pull/866). diff --git a/src/lib/decorators.ts b/src/lib/decorators.ts index cc62160a..001036e6 100644 --- a/src/lib/decorators.ts +++ b/src/lib/decorators.ts @@ -207,6 +207,7 @@ export function internalProperty(options?: InternalPropertyDeclaration) { * `; * } * } + * */ export function query(selector: string) { return (protoOrDescriptor: Object|ClassElement, @@ -225,6 +226,60 @@ export function query(selector: string) { }; } +// Note, in the future, we may extend this decorator to support the use case +// where the queried element may need to do work to become ready to interact +// with (e.g. load some implementation code). If so, we might elect to +// add a second argument defining a function that can be run to make the +// queried element loaded/updated/ready. +/** + * A property decorator that converts a class property into a getter that + * returns a promise that resolves to the result of a querySelector on the + * element's renderRoot done after the element's `updateComplete` promise + * resolves. When the queried property may change with element state, this + * decorator can be used instead of requiring users to await the + * `updateComplete` before accessing the property. + * + * @param selector A DOMString containing one or more selectors to match. + * + * See: https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelector + * + * @example + * + * class MyElement { + * @queryAsync('#first') + * first; + * + * render() { + * return html` + *
+ *
+ * `; + * } + * } + * + * // external usage + * async doSomethingWithFirst() { + * (await aMyElement.first).doSomething(); + * } + */ +export function queryAsync(selector: string) { + return (protoOrDescriptor: Object|ClassElement, + // tslint:disable-next-line:no-any decorator + name?: PropertyKey): any => { + const descriptor = { + async get(this: LitElement) { + await this.updateComplete; + return this.renderRoot.querySelector(selector); + }, + enumerable: true, + configurable: true, + }; + return (name !== undefined) ? + legacyQuery(descriptor, protoOrDescriptor as Object, name) : + standardQuery(descriptor, protoOrDescriptor as ClassElement); + }; +} + /** * A property decorator that converts a class property into a getter * that executes a querySelectorAll on the element's renderRoot. diff --git a/src/test/lib/decorators_test.ts b/src/test/lib/decorators_test.ts index 8640102f..cd79eff4 100644 --- a/src/test/lib/decorators_test.ts +++ b/src/test/lib/decorators_test.ts @@ -13,7 +13,7 @@ */ import {eventOptions, property} from '../../lib/decorators.js'; -import {customElement, html, LitElement, PropertyValues, query, queryAll, queryAssignedNodes} from '../../lit-element.js'; +import {customElement, html, LitElement, PropertyValues, query, queryAll, queryAssignedNodes, queryAsync} from '../../lit-element.js'; import {generateElementName} from '../test-helpers.js'; const flush = @@ -460,6 +460,47 @@ suite('decorators', () => { }); }); + suite('@queryAsync', () => { + + @customElement(generateElementName() as keyof HTMLElementTagNameMap) + class C extends LitElement { + @queryAsync('#blah') blah!: Promise; + + @queryAsync('span') nope!: Promise; + + @property() + foo = false; + + render() { + return html` +
Not this one
+ ${this.foo ? + html`
This one
` : + html`
This one
` } + `; + } + } + + test('returns an element when it exists after update', async () => { + const c = new C(); + container.appendChild(c); + let div = await c.blah; + assert.instanceOf(div, HTMLDivElement); + assert.isFalse(div.hasAttribute('foo')); + c.foo = true; + div = await c.blah; + assert.instanceOf(div, HTMLDivElement); + assert.isTrue(div.hasAttribute('foo')); + }); + + test('returns null when no match', async () => { + const c = new C(); + container.appendChild(c); + const span = await c.nope; + assert.isNull(span); + }); + }); + suite('@eventOptions', () => { test('allows capturing listeners', async function() { if (!supportsOptions) {