Online since 1990 Yes! I started with Gopher. I do modern Web Component Development with technologies supported by **all** WHATWG partners (Apple, Google, Microsoft & Mozilla)
It just acts as a map, and it's not required at all.
I saw that pattern when looking through other web components in the wild, and thought it was a nice way to organize attributes in one place, and maybe even alias them like we've done there.
Online since 1990 Yes! I started with Gopher. I do modern Web Component Development with technologies supported by **all** WHATWG partners (Apple, Google, Microsoft & Mozilla)
Convenience, really. If you wanted to decouple the API from the implementation, this makes it a little more convenient to change things in the future. For example, if I wanted to rename the attribute, I can do it in the attrs object and not have to refactor the rest of the component where I've addressed it.
Do you think there's a better way to do this? The simple alternative I can think of is use the attribute string directly, and then do a find-and-replace if things change.
Online since 1990 Yes! I started with Gopher. I do modern Web Component Development with technologies supported by **all** WHATWG partners (Apple, Google, Microsoft & Mozilla)
Note #1 All good minifiers will replace references with the const value, so you get smaller code as well
Note #2 my-component is often parameterized (is that a word?) as static tagName on the class in recent examples as well. WHY?!? It is available on the component instance as this.localName or this.nodeName
You might have noticed that the timer declaration has moved from the constructor to the connectedCallback, and this was to simplify passing in the dynamic interval delay value, which would only be queryable once the element is mounted (or connected)
There's two things I want to add here:
Setting up the timer in the constructor and removing it in the connectedCallback would have meant the component would have stopped working if it was ever removed from the document and inserted elsewhere. Setting it in the connectedCallback to re-start it when re-attaching is the correct way about this.
It is not entirely correct that the attribute for the delay value is only queryable when the element is mounted. If the element has already been parsed by the time the custom element is defined, the constructor will have full access to the element and its children, including attributes. Only if the custom element is already defined before the element is parsed, the constructor will run before any of the attributes or child elements have been parsed.
Note: This is equivalent static observedAttributes = ["x-interval"];, and if you had more than one attribute to track, you would comma-separate them in that array.
If you already have an attrs attribuet, then I would hope nobody would actually comma-separate them and instead just write something like this:
staticobservedAttributes=Object.keys(Timer.attrs)
Last but not least, the final version of the component would behave somewhat inconsistently. Detaching the component would initially pause the counting. Updating the interval attribute would then resume the counting. Connecting the element again would then start a second interval that could never be cancelled anymore and would continue to count up until the page is closed.
An easy way to fix this would be to just check if the object is actually connected in the attributeChangedCallback 😁
Setting it in the connectedCallback to re-start it when re-attaching is the correct way about this.
This makes so much sense, and I don't know how I missed this!
It is not entirely correct that the attribute for the delay value is only queryable when the element is mounted. If the element has already been parsed by the time the custom element is defined, the constructor will have full access to the element and its children, including attributes. Only if the custom element is already defined before the element is parsed, the constructor will run before any of the attributes or child elements have been parsed.
This has taken me a quite a while to wrap my head around. I had someone else also flag this for me when I did the first post, and to be honest it hasn't clicked until now. I'll work on the correction and update the post. Thank you!
Good shout! I'll add that in too and signpost that by doing so all of your attributes will be tracked, which is probably what you'd want most of the time.
Detaching the component would initially pause the counting. Updating the interval attribute would then resume the counting. Connecting the element again would then start a second interval that could never be cancelled anymore and would continue to count up until the page is closed.
Great catch! I definitely didn't play that scenario out. I'll make the correction!
Honestly, thank you so much for taking the time to read the post and give me feedback! Greatly appreciate it.
One pattern I end up repeating for pretty much every slightly larger component I write is this:
attributeChangedCallback(attribute,from,to){// snake-case to camelCaseconstname=attribute.replaceAll(/-[a-z]/g,str=>str.slice(1).toUpperCase())+"Changed"if (nameinthis)this[name](from,to)}
Then I can just add methods like xIntervalChanged(from, to) { /* ... */ } instead of having a long if in a single method.
Online since 1990 Yes! I started with Gopher. I do modern Web Component Development with technologies supported by **all** WHATWG partners (Apple, Google, Microsoft & Mozilla)
What is the point of attrs ??
over
When
this.constructor.observedAttributes
always gets you the attributes Array (if required at all)It just acts as a map, and it's not required at all.
I saw that pattern when looking through other web components in the wild, and thought it was a nice way to organize attributes in one place, and maybe even alias them like we've done there.
So the next question is: What is the point of aliasing?
Convenience, really. If you wanted to decouple the API from the implementation, this makes it a little more convenient to change things in the future. For example, if I wanted to rename the attribute, I can do it in the
attrs
object and not have to refactor the rest of the component where I've addressed it.Do you think there's a better way to do this? The simple alternative I can think of is use the attribute string directly, and then do a find-and-replace if things change.
Good point "future changes"
You will have a cleaner and smaller class when you declare all that sh* outside the class (at the top of your file)
Note #1 All good minifiers will replace references with the const value, so you get smaller code as well
Note #2
my-component
is often parameterized (is that a word?) asstatic tagName
on the class in recent examples as well.WHY?!? It is available on the component instance as
this.localName
orthis.nodeName
There's two things I want to add here:
Setting up the timer in the constructor and removing it in the
connectedCallback
would have meant the component would have stopped working if it was ever removed from the document and inserted elsewhere. Setting it in theconnectedCallback
to re-start it when re-attaching is the correct way about this.It is not entirely correct that the attribute for the delay value is only queryable when the element is mounted. If the element has already been parsed by the time the custom element is defined, the constructor will have full access to the element and its children, including attributes. Only if the custom element is already defined before the element is parsed, the constructor will run before any of the attributes or child elements have been parsed.
If you already have an
attrs
attribuet, then I would hope nobody would actually comma-separate them and instead just write something like this:Last but not least, the final version of the component would behave somewhat inconsistently. Detaching the component would initially pause the counting. Updating the interval attribute would then resume the counting. Connecting the element again would then start a second interval that could never be cancelled anymore and would continue to count up until the page is closed.
An easy way to fix this would be to just check if the object is actually connected in the
attributeChangedCallback
😁Thank you very much for your feedback.
This makes so much sense, and I don't know how I missed this!
This has taken me a quite a while to wrap my head around. I had someone else also flag this for me when I did the first post, and to be honest it hasn't clicked until now. I'll work on the correction and update the post. Thank you!
Good shout! I'll add that in too and signpost that by doing so all of your attributes will be tracked, which is probably what you'd want most of the time.
Great catch! I definitely didn't play that scenario out. I'll make the correction!
Honestly, thank you so much for taking the time to read the post and give me feedback! Greatly appreciate it.
Your article is a fantastic resource for anyone looking to delve deeper into this subject. I'll definitely be sharing it with my colleagues.
Thank you for your kind words!
One pattern I end up repeating for pretty much every slightly larger component I write is this:
Then I can just add methods like
xIntervalChanged(from, to) { /* ... */ }
instead of having a long if in a single method.Same for Events:
This is definitely a great article! I was delighted with the first part, and then there’s the second! thank you