WebReflection / hyperHTML-Element

An extensible class to define hyperHTML based Custom Elements.
ISC License
202 stars 25 forks source link

dynamic attributes of elements inside other elements are not evaluated in proper time #5

Closed sourcegr closed 7 years ago

sourcegr commented 7 years ago

OK, this might be a silly question, or maybe something is wrong with my understanding.

please see this plunk

The title is not displayed in the first my-title element which is defined in the my-element template. It gets its title attribute from the parent element. The second my-title has a static title, and it shows up as expected.

I was expecting that it would be visible, but it is not. Is this the expected behaviour?

If so, could someone be kind enough to explain me why this is happening and how I could avoid such cases in the future?

In this plunk, I also included a button that, when clicked, updates the my-title element, and then, the title shows up!

Tia,

sourcegr commented 7 years ago

also, note that I am aware that when I include the title in the observedAttributes and then on the attributeChangedCallback I do a this.update();, it works as expected, but I believe this is overhead for the developer, especially on attributes that will probably never change in the my-title element lifecycle!

Once again, please correct me if I am wrong

WebReflection commented 7 years ago

This works, and yes, it is an expected behavior, I explain you why.

class MyTitle extends HyperHTMLElement {
  static get observedAttributes() { return ['title']; }

  attributeChangedCallback() {
    this.update()
  }

  update() {
    return this.html`
      <h2 style="background:red">${this.title}!</h2>
    `;
  }
}

The created() callback is grant to be called once and before connectedCallback() and attributeChangedCallback().

This is a standard behavior lost in V1 but present in V0. Custom Elements V1 don't have any mechanism to setup a component before the rest happens and with random order, since there's no guarantee that connectedCallback will triger before attributeChangedCallback or vice-versa.

HyperHTMLElement grants you an entry point to setup components, so far, so good.

What is a setup

Whenever you need to create a shadow dom, to inject, query, or modify the content or even render, if you don't have any other ways of doing that.

In your case you are creating a component that works through attributes to show its own content.

When you use Custom Elements like that, your explicit intent is to observe attributes and react at those changes. If you don't do so, your component has already an overhead because if it's a one off that you need, drop the observable completely and just:

<my-title>${this.title}</my-title>

because if that's not what you expect, then you expect content changes when attribute changes.

And if you do so, you need attributeChangeCallback that with native Custom Elements triggers once after the component has been setup.

As Summary

If you observe attributes, and you want to react / change layout when these have a value, use attributeChangedCallback as the entry point to render.

If you don't need to render when attributes are changed, drop observed attributes, as easy as that.

Cleaner, faster, better for you own components.

However

If you want automatically do that, which is something this class cannot possibly know or do, unless it forces you to have a render() (or update() ? or maybe populate() ?) method with a fixed signature, you can create your thin layer and define your own fixed behavior with your component.

class MyBase extends HyperHTMLElement {
  attributeChangedCallback() { this.update(); }
  update() {}
}

That's it, from now on all you have to extend is MyBase instead of HyperHTMLElement.

After all, isn't this why classes can also be awesome?

sourcegr commented 7 years ago

After all, isn't this why classes can also be awesome?

yes, one of the few times! ;)

The think is that this is a minimal example, just to describe the situation. There ara times where more than one variable has to be changed or displayed and the <my-title>${this.title}</my-title> solution is not suitable!

Many thanks for your explanation!

WebReflection commented 7 years ago

There are times where more than one variable has to be changed or displayed

And don't you need to re-render the component when these changes happen?

Also, how do you know that an attribute has been set at all, if you don't render on both created and attributeChangedCallback?

You are using native Custom Elements behavior here.

Rather than explaining why it's normal to need rendering eventually on both created and attributeChangedCallback, there's not much else I can do.

Custom Elements are upgraded by specifications. If these have observed attributes and these attributes have a value, the attributeChangedCallback will be triggered.

If you need to change anything on the layout when this happens, you need to render on attributeChangedCallback otherwise you're shooting your own foot :wink:

WebReflection commented 7 years ago

P.S. calling this.update even 60 times per second is safe and fast. The whole point of hyperHTML is that updates are the cheapest thing ever: same values won't modify the DOM at all

sourcegr commented 7 years ago

Thanks for the info!

I understand that this topic is actually beyond the HyperHTML-Element scope so I really appreciate the advice

Keep up the great work!