DEV Community

Web Components: from zero to hero

Pascal Schilp on October 28, 2018

Web components: from zero to hero An introduction to writing raw web components What are web components? A Components lifecy...
Collapse
 
getclibu profile image
Neville Franks

@pascal Thanks for your detailed and informative article on Web Components.

You said: "It's perfectly fine to make web components using light DOM, but you miss out on the great features of shadow DOM."

I've just started playing with Web Components and am struggling with significant differences between using Light DOM and Shadow DOM. With Shadow DOM you need to use <slot>'s to render markup from a parent web component in a child web component. In Light DOM <slot>'s don't exist and I've been unable to get parent markup to render in a child web component using Light DOM.

Further they way you construct html in Shadow DOM seems to be different to Light DOM.

I'm using lit-element FWIW.

Others reading this note that Dev.to has a series of articles by Benny Powers which are definitely worth reading. Part 1 dev.to/bennypowers/lets-build-web-...

Collapse
 
thepassle profile image
Pascal Schilp • Edited

Hi Neville!

When writing raw web components, you can set the markup of the light DOM like so:

class MyElement extends HTMLElement {
  constructor() {
    super();
    this.innerHTML = '<h1>hello world</h1>';
  }
}

Notice how we omit attaching the shadowroot to our element, making it render to the light DOM instead.

In LitElement you can override the createRenderRoot() and return this to make your element render to light dom instead. You're essentially changing the renderroot from the default shadowroot (LitElement defaults to using shadow DOM), to the light dom. You can see an example of that in action here:
stackblitz.com/edit/create-lit-app...

Furthermore; yes, you are correct; when using shadow DOM, you can use slots to render markup from a parent into a child component. The slot API is part of shadow DOM, so you can't have one without the other. (eg: you can't use the slot API with light DOM).

Here's an example of using slots:
stackblitz.com/edit/create-lit-app...

And here's some recommended reading on using slots (the mozilla docs are an incredible reference for anything web components):
developer.mozilla.org/en-US/docs/W...

I hope that's helpful!

I can also vouch for benny's series, I've read them all, and they're all great and showcase really detailed and diverse examples of different web component technologies. :)

Edit: I'm currently working on part 2 of this series, which will talk more about lit-html/element.

Collapse
 
getclibu profile image
Neville Franks

Hi Pascal,
Thanks for your reply. I have been very successfully using Riot.js for quite a while in our Web App Clibu(clibu.com) and would like to move to using Web Components, hence my interest.

I starting playing with Lit-Element a few days ago and I am impressed so far, however as is often the case it is one step forward, two back. ;-)

I am aware of using Light DOM with lit-element which would be an easier initial transition, however I'm currently stuck on using nested components in Light DOM. I've put a sample at stackblitz.com/edit/create-lit-app... and you'll see that <nested-light-dom> doesn't display any content.

Am I right that this isn't possible without using Slots and therefore Shadow DOM?

Thread Thread
 
thepassle profile image
Pascal Schilp

You are correct, you'd have to use slots and shadow DOM to achieve something like this. Do you mind me asking what your use case for this is though? Generally components that use light DOM are simpler leaf/UI components.

Thread Thread
 
getclibu profile image
Neville Franks

Pascal, thanks for the clarification.

What I find somewhat confusing with custom elements is they don't behave like normal elements. For example:

<div>Hello
  <div>Neville</div>
</div>

displays "Hello Neville" whereas:

<custom-element>Hello
  <custom-element>Neville</custom-element>
</custom-element>

Only displays 'Hello'.

I've put up a sample of what I'm trying to accomplish at: stackblitz.com/edit/create-lit-app...

What I want is to have a Web Component that expands & collapses the Web Components nested inside it.

For some reason I don't yet understand the demo app is not working correctly, ie. there is no animation for the transition specified in card-element and the height doesn't reduce to zero.

The same sample without using card-element works perfectly. ie. Replace <card-element> in <container-element> with <div style="transition-...>

I assume that my current lack of knowledge with Shadow DOM will explain the underlying issue, or not. ;-)

Thread Thread
 
thepassle profile image
Pascal Schilp • Edited

Hi Neville, I quickly hacked together this example for you:
stackblitz.com/edit/create-lit-app...

I hope that helps you :-) Feel free to reach out if you have any more questions.

Thread Thread
 
getclibu profile image
Neville Franks

Hi Pascal, that's great and works perfectly and is simpler. Any idea why my code didn't work, just curious.

Thread Thread
 
thepassle profile image
Pascal Schilp • Edited

in card-element.js, change:

const el = this.querySelector( 'h1' )

to:

const el = this.shadowRoot.querySelector( 'div' );

When querying for DOM in web components, you want to target the shadowRoot. Also, you were querying for and changing the height of the h1, while really, you wanted to change the height of the container div.

And you had a bunch of js you didnt really need 😛LitElement will take care of your properties for you. You can use your own getters and setters, but we didn't really need to here. I'll expand more on LitElement in part two of this blog series.

Thread Thread
 
getclibu profile image
Neville Franks • Edited

Thanks again, much to learn about shadow dom and LitElement. Happy enough though for my first week.

Can I suggest you include some downloadable examples in future articles. Stackblitz is awesome as well.

Often we just get pieces of Javascript which are great at explaining things, but when we try and run something like LitElement for the first time we get stuck with build tools and errors in the Browser until we get everything sorted out.

I hadn't found github.com/thepassle/create-lit-app when I started last Monday, which was a pity. I like Parcel.js which is what I'm using, but it took some time to get code that ran without errors.

I'll look forward to your future articles.

PS. My wife is Dutch. Amsterdam is great. ;-)

Thread Thread
 
thepassle profile image
Pascal Schilp • Edited

Yeah, takes some time to get used to web components, but when everything clicks they're well worth it (especially LitElement).

As for downloadable examples, you can find the source code of this blog over here: github.com/thepassle/webcomponents...
(You can run it locally with python -m SimpleHTTPServer 8000, or any other method of serving)

Feel free to reach out if you have any more questions, you can find me on twitter, and usually on the polymer slack.

Collapse
 
rafaferiah profile image
Rafa Romero Dios

Hi @pascal ! First of all thank you for the tutorial, is by far the best web-components tutorial I've faced to!

I have some doubts that I would like to solve :)

In the section Events I don't understand why do you handle the index as attribute instead of property:

// the way you do
set index(val) {
    this.setAttribute('index', val);
}
// the way I though it should be
set index(val) {
    this._index = val;
}

Also in this section, you refer to this.index instead of this._index:

this.dispatchEvent(new CustomEvent('onRemove', { detail: this.index }));

Is it OK or is it a mistake?

And the last one. In the section Reflecting properties to attributes I have the same problem with setters that I had with index. Why do you handle the checked as attribute instead of property??

// the way you do
get checked() {
    return this.hasAttribute('checked');
}

set checked(val) {
    if (val) {
        this.setAttribute('checked', '');
    } else {
        this.removeAttribute('checked');
    }
}

// the way I though it should be
get checked() {
    return this._checked;
}

set checked(val) {
    this._checked = val === true;
}

Many thanks in advance and congrats for your work!

Collapse
 
thepassle profile image
Pascal Schilp

Hi Rafa, this is mostly because attribution/property syncing. Arguably the index property doesn't need to be an attribute, but it's a good showcase on how to sync attributes with properties, and how to deal with different types than Strings.

If you'd set set index(val) { this._index = val }, the attribute won't be up to date with the property. When getting the index property, we can just read the attribute's value.

Same goes for the checked property, you'll almost definitely want the attribute to stay in sync with property, and its a showcase on how to handle boolean attributes as well.

Hope that clarifies — feel free to let me know if you have any more questions 😊

Collapse
 
rafaferiah profile image
Rafa Romero Dios • Edited

Hi Pascal! Thanks for your quick answer!

I understand what you say, but I thought that it was necessary to maintain a "model" (based on properties) that is mapped to all the attributes. That's why I thought that all the getters and setter should point to properties, instead of attributes.

Would it be possible to do it that way? I mean, maintain a model (properties based) and at the same time maintain up to date the syncronization between attributes and properties?

In the other hand, I understand then that attribute-properties mapped is not always mandatory, depending or you component logic, right?

Collapse
 
sturzl profile image
Avery • Edited

After a week with one dev pair trying to build a web component, this is the best post anyone on our team can find on the internet.

Every other web component tutorial seems like the author doesn't even have working code, and they miss important steps like how to include css.

Collapse
 
lorless profile image
Lorless

Hi Pascal,

It seems the examples of the code running in stackblitz have no reference to index.js. It is never included in the page. How is the custom element code even running? The only script links are to node_modules.

Collapse
 
thepassle profile image
Pascal Schilp

Hey Lorless, stackblitz does some magic that adds the index.js. If you're trying it out locally you should add a <script type="module" src="./index.js"> to your index.html.

Collapse
 
lorless profile image
Lorless • Edited

Thanks! I wondered whether something like that was happening. The tutorial was good.

Collapse
 
tiho2 profile image
tiho2 • Edited

I'd additionally install es-dev-server as explained here. In case local development environment is not already set.

Collapse
 
webdva profile image
webdva

I've always wondered about web components and this well-written post seems like the perfect introduction to learning how to implement web components.

Collapse
 
3dsn profile image
Edson Barbosa • Edited

Hi Pascal, first of all it's a really nice article.

You said: We've currently set it only as an attribute, but we would like to have it available as a property as well. This is called reflecting properties to attributes.

Reflecting term is used vice versa or would be attribs to props ?

Collapse
 
thepassle profile image
Pascal Schilp • Edited

Hi Edson!

Good catch! That should be ‘reflecting properties to attributes’, we reflect a property to be available as an attribute on the node

Collapse
 
3dsn profile image
Edson Barbosa

tks man ... I found today another good explantion from aligator.io.

"alligator.io/web-components/attrib..."

Collapse
 
jimisdrpc profile image
jimisdrpc

Pascal, supposing you had a backend, how would you consume it? More prreciselly, supposing you need to persist the to-do list and validate each new to-do against some backend rule, how would do it? I guess you would need some third library, right? Which one would you suggest if you have to choose between ReactJs, Redux or litelement?

Collapse
 
thepassle profile image
Pascal Schilp

Hey — I somehow missed this post, sorry about that. In a more real life example, you could probably do a request to a backend using fetch or axios or whatever you prefer to use. You can use Lit-Element as your app/component model, and you can even add Redux to that if you need it.

Collapse
 
ahmadchaker profile image
AhmadChaker • Edited

Hi Pascal, I have a few questions. In the github code example for this you have the following index.html:

github.com/thepassle/webcomponents...

I don't understand why you would need import('./to-do-app.js'). You have imported the component at the top and in-fact you have used it already in the html body.
I've stepped through with the debugger and it looks like the constructor of to-do-app.js is not even invoked until after the script is finished, this is despite the fact that the html is already used in the html body!
Can you explain what is happening here?

Collapse
 
mvoloskov profile image
Miloslav 🏳️‍🌈 🦋 Voloskov

This is brilliant. Probably the only tutorial you'll need to start.

Good job.

Collapse
 
newlegendmedia profile image
Jeff Hilton

Hi! Thanks for this extremely well organized and detailed walk thru of a basic web component. It has cemented a lot of disparate information for me. I get it now. Thanks!

Collapse
 
thepassle profile image
Pascal Schilp

Thanks, I'm glad to hear that!

Collapse
 
leoplaw profile image
leoplaw

What about observing a component size change? In theory ResizeObserver should work, but so far it fails.
jsfiddle.net/h15e48fx/2/

Collapse
 
tiho2 profile image
tiho2 • Edited

Hi,
thanks for the great tutorial. I wonder how do you input font awesome symbols/characters to template directly, eg. the button:?