Skip to content

Commit 672e457

Browse files
author
Steve Orvell
authored
Merge pull request #904 from Polymer/asyncQuery
Adds `@queryAsync` decorator
2 parents 628711a + ec0f2dc commit 672e457

File tree

3 files changed

+98
-1
lines changed

3 files changed

+98
-1
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
2222
* The value returned by `render` is always rendered, even if it isn't a `TemplateResult`. ([#712](https://github.com/Polymer/lit-element/issues/712)
2323

2424
### Added
25+
* 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)).
2526
* Added `enableUpdating()` to `UpdatingElement` to enable customizing when updating is enabled [#860](https://github.com/Polymer/lit-element/pull/860).
2627
* Added `queryAssignedNodes(slotName, flatten)` to enable querying assignedNodes for a given slot [#860](https://github.com/Polymer/lit-element/pull/860).
2728
* Added `getStyles()` to `LitElement` to allow hooks into style gathering for component sets [#866](https://github.com/Polymer/lit-element/pull/866).

src/lib/decorators.ts

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,7 @@ export function internalProperty(options?: InternalPropertyDeclaration) {
207207
* `;
208208
* }
209209
* }
210+
*
210211
*/
211212
export function query(selector: string) {
212213
return (protoOrDescriptor: Object|ClassElement,
@@ -225,6 +226,60 @@ export function query(selector: string) {
225226
};
226227
}
227228

229+
// Note, in the future, we may extend this decorator to support the use case
230+
// where the queried element may need to do work to become ready to interact
231+
// with (e.g. load some implementation code). If so, we might elect to
232+
// add a second argument defining a function that can be run to make the
233+
// queried element loaded/updated/ready.
234+
/**
235+
* A property decorator that converts a class property into a getter that
236+
* returns a promise that resolves to the result of a querySelector on the
237+
* element's renderRoot done after the element's `updateComplete` promise
238+
* resolves. When the queried property may change with element state, this
239+
* decorator can be used instead of requiring users to await the
240+
* `updateComplete` before accessing the property.
241+
*
242+
* @param selector A DOMString containing one or more selectors to match.
243+
*
244+
* See: https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelector
245+
*
246+
* @example
247+
*
248+
* class MyElement {
249+
* @queryAsync('#first')
250+
* first;
251+
*
252+
* render() {
253+
* return html`
254+
* <div id="first"></div>
255+
* <div id="second"></div>
256+
* `;
257+
* }
258+
* }
259+
*
260+
* // external usage
261+
* async doSomethingWithFirst() {
262+
* (await aMyElement.first).doSomething();
263+
* }
264+
*/
265+
export function queryAsync(selector: string) {
266+
return (protoOrDescriptor: Object|ClassElement,
267+
// tslint:disable-next-line:no-any decorator
268+
name?: PropertyKey): any => {
269+
const descriptor = {
270+
async get(this: LitElement) {
271+
await this.updateComplete;
272+
return this.renderRoot.querySelector(selector);
273+
},
274+
enumerable: true,
275+
configurable: true,
276+
};
277+
return (name !== undefined) ?
278+
legacyQuery(descriptor, protoOrDescriptor as Object, name) :
279+
standardQuery(descriptor, protoOrDescriptor as ClassElement);
280+
};
281+
}
282+
228283
/**
229284
* A property decorator that converts a class property into a getter
230285
* that executes a querySelectorAll on the element's renderRoot.

src/test/lib/decorators_test.ts

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
*/
1414

1515
import {eventOptions, property} from '../../lib/decorators.js';
16-
import {customElement, html, LitElement, PropertyValues, query, queryAll, queryAssignedNodes} from '../../lit-element.js';
16+
import {customElement, html, LitElement, PropertyValues, query, queryAll, queryAssignedNodes, queryAsync} from '../../lit-element.js';
1717
import {generateElementName} from '../test-helpers.js';
1818

1919
const flush =
@@ -460,6 +460,47 @@ suite('decorators', () => {
460460
});
461461
});
462462

463+
suite('@queryAsync', () => {
464+
465+
@customElement(generateElementName() as keyof HTMLElementTagNameMap)
466+
class C extends LitElement {
467+
@queryAsync('#blah') blah!: Promise<HTMLDivElement>;
468+
469+
@queryAsync('span') nope!: Promise<HTMLSpanElement|null>;
470+
471+
@property()
472+
foo = false;
473+
474+
render() {
475+
return html`
476+
<div>Not this one</div>
477+
${this.foo ?
478+
html`<div id="blah" foo>This one</div>` :
479+
html`<div id="blah">This one</div>` }
480+
`;
481+
}
482+
}
483+
484+
test('returns an element when it exists after update', async () => {
485+
const c = new C();
486+
container.appendChild(c);
487+
let div = await c.blah;
488+
assert.instanceOf(div, HTMLDivElement);
489+
assert.isFalse(div.hasAttribute('foo'));
490+
c.foo = true;
491+
div = await c.blah;
492+
assert.instanceOf(div, HTMLDivElement);
493+
assert.isTrue(div.hasAttribute('foo'));
494+
});
495+
496+
test('returns null when no match', async () => {
497+
const c = new C();
498+
container.appendChild(c);
499+
const span = await c.nope;
500+
assert.isNull(span);
501+
});
502+
});
503+
463504
suite('@eventOptions', () => {
464505
test('allows capturing listeners', async function() {
465506
if (!supportsOptions) {

0 commit comments

Comments
 (0)