Webcomponents: [idea] Expose and API for syncing attributes and properties

Created on 28 Jun 2019  路  6Comments  路  Source: WICG/webcomponents

There should be a built-in mechanism for keeping custom element attributes and properties in sync. This is currently only possible using getters/setters and potentially problematic logic in the attributeChangedCallback. Doing this would remove the amount of necessary boilerplate code and potentially clear up some of the confusion in the wild about the differences between attributes and properties on HTML elements.

custom-elements needs concrete proposal

Most helpful comment

I would love to see this put in the queue behind decorators and built-in modules so that one we have both of those browsers can offer a built-in @reflect decorator:

import { @reflect } from 'std:custom-elements';

class MyElement extends HTMLElement {
  @reflect({name: 'my-attr'}) myAttr;
}

This is something @domenic has talked about before.

The tricky details here are around whether there's a callback for property changes, and deserialization. to non-string types. Setters already _are_ a callback for property changes, so arguably this need is taken care of, but in practice you often just want to pipe the change through one common callback to update the host.

All 6 comments

I would love to see this put in the queue behind decorators and built-in modules so that one we have both of those browsers can offer a built-in @reflect decorator:

import { @reflect } from 'std:custom-elements';

class MyElement extends HTMLElement {
  @reflect({name: 'my-attr'}) myAttr;
}

This is something @domenic has talked about before.

The tricky details here are around whether there's a callback for property changes, and deserialization. to non-string types. Setters already _are_ a callback for property changes, so arguably this need is taken care of, but in practice you often just want to pipe the change through one common callback to update the host.

I definitely think a decorator is a good choice here, but really don't like the built-in modules (I personally think that proposal is just unnecessary abstraction, but that's just me).

Could that be put inside of something like customElements instead?

const { reflect } = customElements;

class MyElement extends HTMLElement {
  @reflect({ name: 'my-attr' }) myAttr;
}

As for the property change callback, I am happy with just using setters, but even using the example you put forward, you could drop an updated callback inside the decorator invocation:

const { reflect } = customElements;

class MyElement extends HTMLElement {
  @reflect({ 
    name: 'my-attr',
    callback: this.#updateMyAttr.bind(this)
  }) myAttr;

  #updateMyAttr(value, key) { /** Do something really interesting */ }
}

Thinking on this further, should this feature (which is pretty low-level) be blocked by something like decorators? I think a decorator would provide a useful abstraction eventually, but why not add something like this to ElementInternals or as another static property on the element class?

Decorators are still at stage two, blocking a useful feature waiting for a syntax proposal to be added to the language (which may or may not actually happen, much less in the next two years) seems counterproductive.

Honestly, I believe the current low level API is fine, user-land abstractions can take care of this. I rather prefer to put more effort on enabling other low level APIs than starting to add high level APIs. Once we have decorators, we can come back and look at the ergonomics, and simplify a bunch of APIs.

I somewhat agree, but I can also see the benefit of reusing the browser's internal logic to get consistent behaviour between native element attributes and custom element attributes. Not all engineers I've worked with on custom elements have understood the importance of such consistency and setting attributes or linked properties results in unexpected behaviour.

Providing a very easy to use decorator that gets you the same semantics as native attribute-property links could encourage good practices and even provide use of otherwise unavailable interfaces such as DOMTokenList. For example:

class MyCarousel extends HTMLElement {
  @attr('autonewslide', { type: 'boolean' }) autoNewSlide;
  @attr('slidedirection', { type: 'tokenlist' }) slideDirection;
  @attr('waittime', { type: 'custom', parser: fn, serializer: fn }) waitTime;
  @attr('onnewslide', { type: 'function' }) onsomething;
}

Note: event handler attributes have particularly strange semantics (setting the property does not update the attribute and setting the attribute does not override a previously set property), and there's possibly an argument against enabling those kinds of attributes anyway.

Could that be put inside of something like customElements instead?

Nope, unlike previous iterations of decorators in the current proposal decorators are not values, they're a separate namespace thing all together.

The consequence of this is all decorators need to either be global or put into a module.

The motivation behind this is previous iterations of the decorator proposal basically weren't very useful for engines (or tools) to optimize as they couldn't be statically analyzed, by having all decorators known ahead of time engines (and tools) can do stuff ahead of time as well.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

esarbe picture esarbe  路  8Comments

justinfagnani picture justinfagnani  路  8Comments

hayatoito picture hayatoito  路  3Comments

travisleithead picture travisleithead  路  8Comments

dandclark picture dandclark  路  5Comments