Skip to content

Conversation

@sorvell
Copy link
Member

@sorvell sorvell commented Feb 24, 2020

Fixes #903 by adding an @asyncQuery decorator.

Steven Orvell added 3 commits February 11, 2020 15:34
Returns a promise this which resolves to the result of `querySelector` the given `selector` performed after the element's `updateComplete` promise is resolved.
const el = this.renderRoot.querySelector(selector);
// if this is a LitElement, then await updateComplete
if (el) {
await (el as LitElement).updateComplete;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this good/required?

It sort of confuses the question "why should a user use asyncQuery?"

  1. Because you need a reference to a child element but it won't be there until after this element flushes?
  2. Because you've changed state on the child element and for some reason don't want to interact with it until after it's flushed?

The latter should be rare; flushing a child should normally be unobservable except for measurement (and we know that's fraught due to nested/chained updates). It's sort of lumping two use cases together, and given the second one is problematic, just want to discuss whether the child await is defensible vs. just pushing the entire measurement case to user responsibility.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This also causes another microtask wait for every use, regardless of whether the element is a LitElement. And it doesn't work for other asynchronously rendering components like Stencil.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yup, removing this part.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is potentially very useful if made a bit more flexible:

export function asyncQuery(selector: string, waitFor?: (el) => Promise<unknown>) {
  if (waitFor) { await waitFor(el); }
...

Then one could do:

@asyncQuery('#foo', (e) => e.updateComplete) foo;

Or the equivalent for Stencil...

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added a comment about supporting this in the future.

}

/**
* A property decorator that converts a class property into a getter that
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this preferable over:

class {
  @query('#foo') foo;

  x() {
    await this.updateComplete;
    this.foo;
  }
}

ie, when would we tell someone to use one pattern over the other?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inside the element, this is arguably just slightly sugared. For a property that needs to be accessed external to the element, using @asyncQuery is much cleaner since the user doesn't have to know about updateComplete.

Copy link
Contributor

@justinfagnani justinfagnani Feb 27, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we have use cases where an element wants to expose part of its internals like this?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is useful when a property that is a query can change with element state. It's preferable when the property is public so that the user does not have to know about the updateComplete promise.

const el = this.renderRoot.querySelector(selector);
// if this is a LitElement, then await updateComplete
if (el) {
await (el as LitElement).updateComplete;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This also causes another microtask wait for every use, regardless of whether the element is a LitElement. And it doesn't work for other asynchronously rendering components like Stencil.

*
* class MyElement {
* @asyncQuery('#first')
* first;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the example should show the usage of this.first somewhere too.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

Copy link
Contributor

@justinfagnani justinfagnani left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

Only other thought is the name. I've gotten used to sync/async modifiers coming at the end of names. How's @queryAsync() sound?

const el = this.renderRoot.querySelector(selector);
// if this is a LitElement, then await updateComplete
if (el) {
await (el as LitElement).updateComplete;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is potentially very useful if made a bit more flexible:

export function asyncQuery(selector: string, waitFor?: (el) => Promise<unknown>) {
  if (waitFor) { await waitFor(el); }
...

Then one could do:

@asyncQuery('#foo', (e) => e.updateComplete) foo;

Or the equivalent for Stencil...

@sorvell sorvell changed the title Adds @asyncQuery decorator Adds @queryAsync decorator Feb 28, 2020
@sorvell sorvell merged commit 672e457 into master Feb 28, 2020
@sorvell sorvell deleted the asyncQuery branch February 28, 2020 18:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add @queryAsync decorator

4 participants