All the major front-end frameworks that developers use today have something in common. They all use components as basic blocks for building the UI. In Chapter 2, you saw how to create a component registry based on pure functions. On (almost) all modern browsers, it’s possible to create components for your web applications with a suite of native APIs known as web components.
The APIs
HTML templates. The <template> tag is useful if you want to keep content that is not rendered, but may be used by JavaScript code as a “stamp” to create dynamic content.
Custom elements. This API lets developers create their own fully featured DOM elements.
Shadow DOM: This technique is useful if the web components should not be affected by the DOM outside the component itself. It’s very useful if you’re creating a component library or a widget that you want to share with the world.
Caution
The shadow DOM and the virtual DOM solve two completely different problems. The shadow DOM is about encapsulation, whereas the virtual DOM is about performances. For more information, I suggest reading the post at https://develoger.com/shadow-dom-virtual-dom-889bf78ce701 .
Can I Use It?
Status of Web Components Adoption (early 2019)
API Supported | Chrome | Firefox | Safari | Edge | Internet Explorer |
---|---|---|---|---|---|
HTML templates | Yes | Yes | Yes | Yes | No |
Shadow DOM | Yes | Yes | Yes | Developing | No |
Custom elements | Yes | Yes | Yes | Developing | No |
I talked about HTML templates in Chapter 3. We used it in the last implementation of our rendering engine. Shadow DOM is beyond the scope of this chapter. I suggest reading the MDN tutorial about it at https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_shadow_DOM .
Custom Elements
It is no coincidence that I used the name app-calendar. When you create a custom tag with the Custom Elements API, you have to use at least two words separated by a dash. Every one-word tag is for the sole use of the World Wide Web Consortium (W3C). In Listing 4-1, you see the simplest custom element possible: a “Hello World!” label.
Note
A custom element is just a JavaScript class that extends HTML elements.
HelloWorld Custom Element
connectedCallback is one of the lifecycle methods of a custom element. This method is invoked when the component is attached to the DOM. It’s very similar to the componentDidMount method in React. It’s a good place to render the content of the component, like in our case, or to start timers or fetch data from the network. Similarly, the disconnectedCallback is invoked when the component is removed from the DOM, which is a useful method in any cleanup operation.
Adding HelloWorld to Custom Elements Registry
To add a component to the browser component registry means connecting a tag name ('hello-world' in our case) to a custom element class. After that, you can simply use the component with the custom tag that you created (<hello-world/>).
Managing Attributes
The most important feature of web components is that developers can make new components that are compatible with any framework; not just with React or Angular, but any web application, including legacy applications built with JavaServer Pages or some other older tool. But, to achieve this goal, the components need to have the same public API as any other standard HTML element. So if we want to add an attribute to a custom element, we need to be sure that we can manage this attribute in the same way as any other attribute. For a standard element like <input>, we can set an attribute in three ways.
Each of these three methods accomplishes the same result: it changes the value attribute of the input element. They are also synchronized. If I input the value via the markup, I will read the same value with the getter or the getAttribute method . If I change the value with the setter or the setAttribute method, the markup will synchronize with the new attribute.
HelloWorld with an Attribute
As you can see, the color getter/setter is just a wrapper about getAttribute/setAttribute. So, the three ways to set an attribute are automatically synchronized.
Using the color Attribute for the HelloWorld Component
Using this approach when designing attributes makes it easy for other developers to release the component. We only need to release the code of the component in a CDN, and then everyone could use it without any specific instructions. We just defined an attribute in the same way that the W3C did for standard components.
Nevertheless , this approach comes with a drawback: HTML attributes are strings. So when you need an attribute that is not a string, you have to first convert the attribute.
But, this strong constraint is really useful just for components that need to be published to other developers. In a real-world application based on web components, you may have a lot of components that are not meant to be published. They are “private” to your application. In these cases, you can just use a setter without converting the value to a string.
attributeChangedCallback
Changing the Color of the HelloWorld Component
Updating the Color of the Label
The attributeChangedCallback method accepts three parameters: the name of the attribute that has changed, the attribute’s old value of the attribute, and the attribute’s new value.
Note
Not every attribute will trigger attributeChangedCallback, only the attributes listed in the observedAttributes array.
Virtual DOM Integration
Using Virtual DOM in a Custom Element
Using a virtual DOM for this scenario is clearly over-engineering, but it can be useful if your component has a lot of attributes. In a case like that, the code would be much more readable.
Custom Events
GitHubAvatar Component
What if we want to react to the result of the HTTP request from the outside of the component itself? Remember that when it’s possible, a custom element should behave exactly like a standard DOM element. Earlier, we used attributes to pass information to a component, just like any other element. Following the same reasoning to get information from a component, we should use DOM events. In Chapter 3, I talked about the Custom Events API, which makes it possible to create DOM events that are bounded to the domain and not the user interaction with the browser.
GitHubAvatar with Custom Events
Attaching Event Handlers to GitHubAvatar Events
Using Web Components for TodoMVC
HTML for TodoMVC Application with Web Components
TodoMVC List Web Component
Most of this code is very similar to the one in Chapter 3. One of the differences is that we use a custom event to tell the outer world what is happening when the user clicks the Destroy button. The only attribute that this component accepts as input is the list of todo items; every time the attribute changes, the list is rendered. As you saw earlier in this chapter, it’s very easy to attach a virtual DOM mechanism in here.
TodoMVC Application Components
This component has no attributes; it has an internal state instead. Events from the DOM (standard or custom) change this state, and then the component syncs its state with the attributes of its children in the syncAttributes method. I talk more about which components should have an internal state in Chapter 7.
Web Components vs. Rendering Functions
Now that you have seen web components in action, let’s compare them to the rendering functions approach that we analyzed in Chapter 2 and Chapter 3. Next, I discuss some of the pros and cons of these two ways to render DOM elements.
Code Style
To create a web component means to extend an HTML element, so it requires you to work with classes. If you’re a functional programming enthusiast, you may feel uncomfortable working in this way. On the other hand, if you’re familiar with languages based on classes like Java or C#, you may feel more confident with web components than functions.
There is no real winner here; it’s really up to what you like most. As you saw in the last TodoMVC implementation, you can take your rendering functions and wrap them with web components over time so that you can adapt your design to your scenario. For example, you can start with simple rendering functions and then wrap them in a web component if you need to release them in a library.
Testability
To easily test rendering functions, you only need a test runner integrated with a JSDOM like Jest ( https://jestjs.io ). JSDOM is a mock DOM implementation used for Node.js that is extremely useful for testing rendering. The problem is that JSDOM doesn’t support (for now) custom elements. To test a custom element, you may need to use a real browser with a tool like Puppeteer ( https://developers.google.com/web/tools/puppeteer ), but your tests will be slower and likely more complicated.
Portability
Web components exist to be portable. The fact that they act exactly like any other DOM element is a killer feature if you need to use the same component between other applications.
Community
Component classes are a standard way to create DOM UI elements in most frameworks. This is a very useful thing to keep in mind if you have a large team or a team that needs to grow quickly. Your code is more readable if it is similar to other code that people are familiar with.
Disappearing Frameworks
A very interesting side effect of the emergence of web components is the birth of a bunch of tools that are called disappearing frameworks (or invisible frameworks). The basic idea is to write code like with any other UI framework, like React. When you create the production bundle, the output will be standard web components. In other words, during compile time, the framework will simply dissolve.
A Simple Stencil.js Component
Summary
In this chapter, you learned about the main APIs behind the web component standard and explored the Custom Elements API.
We built a new version of our TodoMVC application based on web components, and we evaluated the differences between this approach and rendering functions.
Finally, you learned about disappearing frameworks and saw how to create a very simple component with Stencil.js.
The next chapter focuses on building a frameworkless HTTP client to make asynchronous requests.