{"id":2012,"date":"2021-07-14T17:33:25","date_gmt":"2021-07-15T00:33:25","guid":{"rendered":"https:\/\/coderpad.io\/?p=2012"},"modified":"2023-06-07T14:17:14","modified_gmt":"2023-06-07T21:17:14","slug":"intro-to-web-components-vanilla-js","status":"publish","type":"post","link":"https:\/\/coderpad.io\/blog\/development\/intro-to-web-components-vanilla-js\/","title":{"rendered":"Web Components 101: Vanilla JavaScript"},"content":{"rendered":"\n<p>Many modern web apps today are built using components. While frameworks like React exist to add an implementation, web components seek to make those practices standardized and part of your browser.<\/p>\n\n\n\n<p>In this article, we\u2019ll touch on what web components are, how we can build them without a framework, and some limitations to keep in mind during development. Later, in a follow-up article, we\u2019ll show how a lightweight framework (such as Lit) can provide quality-of-life improvements for those looking to build larger scale applications.<\/p>\n\n\n<aside class=\"\n    cta-banner\n     cta-banner--bg-blue      cta-banner--has-media \"\ndata-block-name=\"cta-banner\">\n    <div class=\"inner\">\n        <div class=\"content\">\n                            <h2 class=\"headline\">Learn how to run front-end developer interviews that don&#8217;t suck<\/h2>\n            \n                            <div class=\"cta-buttons\">\n                                    <a href=\"https:\/\/coderpad.io\/blog\/interviewing\/5-tips-for-interviewing-frontend\/\" class=\"button  js-cta--read-our-guide\"  data-ga-category=\"CTA\" data-ga-label=\"Learn how to run front-end developer interviews that don&#039;t suck|Read our guide\">Read our guide<\/a>\n                                <\/div>\n                    <\/div>\n                    <div class=\"media\">\n                <img loading=\"lazy\" decoding=\"async\" width=\"432\" height=\"342\" src=\"https:\/\/coderpad.io\/wp-content\/uploads\/2022\/08\/Illustration-of-man-with-beard-popping-out-of-computer-chat.png\" class=\"attachment-large size-large\" alt=\"\" srcset=\"https:\/\/coderpad.io\/wp-content\/uploads\/2022\/08\/Illustration-of-man-with-beard-popping-out-of-computer-chat.png 432w, https:\/\/coderpad.io\/wp-content\/uploads\/2022\/08\/Illustration-of-man-with-beard-popping-out-of-computer-chat-300x238.png 300w\" sizes=\"auto, (max-width: 432px) 100vw, 432px\" \/>\n            <\/div>\n            <\/div>\n<\/aside>\n\n\n\n<h2 class=\"wp-block-heading\">What are Web Components?<\/h2>\n\n\n\n<p>There are a lot of misconceptions about what web components even are. While some might assume that it\u2019s simply the ability to make custom elements with dedicated UI, style, and logic in one consolidated place (more on that later), there\u2019s definitely more to it<br><\/p>\n\n\n\n<p>Web components are a mix of 3 different web standards that, when utilized together, can offer a viable alternative to using a framework like React which offers similar functionality. These web standards consist of:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li><a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/Web_Components\/Using_custom_elements\" target=\"_blank\" rel=\"noopener\">Custom elements<\/a> &#8211; the ability to create new elements that will provide unique UI and app logic when the related HTML tag is added<\/li>\n\n\n\n<li><a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/Web_Components\/Using_shadow_DOM\" target=\"_blank\" rel=\"noopener\">Shadow DOM<\/a> &#8211; the ability to keep specific elements segmented off from your main document DOM, allowing you to avoid document collision issues<\/li>\n\n\n\n<li><a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/Web_Components\/Using_templates_and_slots\" target=\"_blank\" rel=\"noopener\">HTML templates<\/a> &#8211; elements that allow you to write HTML that is not drawn to the page, but can be used as a template for markup to reuse elsewhere<\/li>\n<\/ol>\n\n\n\n<p>While the Shadow DOM and HTML templates are undoubtedly useful in applications, we\u2019ll be focusing on custom elements today, as we feel they\u2019re the easiest place to start in introducing web components as a whole.<\/p>\n\n\n\n<blockquote class=\"wp-block-quote\">\n<p>While these are the only official specifications part of Web Components, they\u2019re often utilized with other JavaScript and browser features to create a cohesive development experience.<\/p>\n\n\n\n<p>One of these features often used is <a href=\"https:\/\/v8.dev\/features\/modules\" target=\"_blank\" rel=\"noopener\">JavaScript Modules<\/a>. While the concept of breaking your app into multiple files has been commonplace with bundlers like Webpack for a while, being built into the browser has been game changing.<\/p>\n<\/blockquote>\n\n\n\n<h2 class=\"wp-block-heading\">What are Custom Elements?<\/h2>\n\n\n\n<p>At their core, custom elements essentially allow you to create new HTML tags. These tags are then used to implement custom UI and logic that can be used throughout your application.&nbsp;<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-1\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml shcb-wrap-lines\"><span class=\"hljs-comment\">&lt;!-- page.html --&gt;<\/span>\n\n<span class=\"hljs-comment\">&lt;!-- These are custom elements, combined to make a page --&gt;<\/span>\n<span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">page-header<\/span>&gt;<\/span><span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">page-header<\/span>&gt;<\/span>\n<span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">page-contents<\/span>&gt;<\/span><span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">page-contents<\/span>&gt;<\/span>\n<span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">page-footer<\/span>&gt;<\/span><span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">page-footer<\/span>&gt;<\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-1\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>These components can be as simple as a styled button or as complex as an entire page of your application, complete with your business logic.<\/p>\n\n\n\n<p>While we tend to think of HTML tags as directly mapping to a single DOM element, that\u2019s not always the case with custom elements. For example, the \u201cpage-header\u201d tag in the example above might contain \u201cnav\u201d and \u201ca\u201d elements as a list of their children.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"452\" height=\"224\" src=\"https:\/\/coderpad.io\/wp-content\/uploads\/2021\/07\/pasted-image-0.png\" alt=\"\" class=\"wp-image-2071\" srcset=\"https:\/\/coderpad.io\/wp-content\/uploads\/2021\/07\/pasted-image-0.png 452w, https:\/\/coderpad.io\/wp-content\/uploads\/2021\/07\/pasted-image-0-300x149.png 300w\" sizes=\"auto, (max-width: 452px) 100vw, 452px\" \/><\/figure>\n\n\n\n<p>Because of this, we\u2019re able to improve an app\u2019s organization by reducing the amount of tags visible in a single file to read with better flow.&nbsp;<\/p>\n\n\n\n<p>But custom elements aren\u2019t just made up of HTML &#8211; you\u2019re able to associate JavaScript logic with these tags as well! This enables you to keep your logic alongside it\u2019s associated UI. Say your header is a dropdown that\u2019s powered by JavaScript. Now you can keep that JavaScript inside of your \u201cpage-header\u201d component, keeping your logic consolidated.<\/p>\n\n\n\n<p>Finally, a significant improvement that components provide is composability. You\u2019re able to use these components on different pages, allowing you to keep your header code in sync between pages. This reduces the potential for having variations in standard components &#8211; like having multiple differently sized buttons in a page &#8211; that might confuse your users. As long as you\u2019re vigilant about utilizing your existing components, you\u2019re able to make your app more consistent this way.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">History<\/h2>\n\n\n\n<p>But web components didn\u2019t come from nowhere. While web components enjoy large-scale usage now, that wasn\u2019t always the case. Let\u2019s walk through a short history of web components and the related ecosystem.<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>2010:\n<ul class=\"wp-block-list\">\n<li>Angular.js made open-source<br><\/li>\n<\/ul>\n<\/li>\n\n\n\n<li>2011:\n<ul class=\"wp-block-list\">\n<li><a href=\"https:\/\/fronteers.nl\/congres\/2011\/sessions\/web-components-and-model-driven-views-alex-russell\" target=\"_blank\" rel=\"noopener\">Web components are announced at a conference by Alex Russell<\/a> (then Sr Staff Engineer at Google, working on web platform team)<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n\n\n\n<ul class=\"wp-block-list\">\n<li>2013:\n<ul class=\"wp-block-list\">\n<li><a href=\"https:\/\/www.youtube.com\/watch?v=DH1vTVkqCDQ\" target=\"_blank\" rel=\"noopener\">Polymer (Google\u2019s web component framework) public development began<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/www.youtube.com\/watch?v=GW0rj4sNH2w\" target=\"_blank\" rel=\"noopener\">React open-sourced<\/a><\/li>\n<\/ul>\n<\/li>\n<\/ul>\n\n\n\n<ul class=\"wp-block-list\">\n<li>2016:\n<ul class=\"wp-block-list\">\n<li><a href=\"https:\/\/blog.youtube\/news-and-events\/a-sneak-peek-at-youtubes-new-look-and\/\" target=\"_blank\" rel=\"noopener\">YouTube rewritten in Polymer<\/a><\/li>\n<\/ul>\n<\/li>\n<\/ul>\n\n\n\n<ul class=\"wp-block-list\">\n<li>2018:\n<ul class=\"wp-block-list\">\n<li><a href=\"https:\/\/www.polymer-project.org\/blog\/2018-05-02-roadmap-update#libraries\" target=\"_blank\" rel=\"noopener\">Polymer announces start of migration to \u201cLitElement\u201d<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/www.mozilla.org\/en-US\/firefox\/63.0\/releasenotes\/\" target=\"_blank\" rel=\"noopener\">Firefox enables web components (Polyfills no longer needed)<\/a><\/li>\n<\/ul>\n<\/li>\n<\/ul>\n\n\n\n<p>While JavaScript frameworks with similar concepts have been around since at least 2010, web components have found a way to standardize those concepts in the browser.&nbsp;<\/p>\n\n\n\n<p>it\u2019s clear that the core concepts at play in web components have allowed for dramatic adoption since then. For example React, which has a lot of the same ideas at play, now has a major market share of websites and applications written in JavaScript.&nbsp;<\/p>\n\n\n\n<p>Now that we\u2019ve seen a short history of web components, let\u2019s take a look at how to build custom elements without using a framework.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Lifecycle Methods<\/h2>\n\n\n\n<p>While many implementations of components have differences, one concept that is fairly universal is \u201clifecycle methods\u201d. At their core, lifecycle methods enable you to run code when events occur on an element. Even frameworks like React, which haved moved away from classes, still have similar concepts of doing actions when a component is changed in some way.<\/p>\n\n\n\n<p><br>Let\u2019s take a look at some of the lifecycle methods that are baked into the browser\u2019s implementation.<\/p>\n\n\n\n<p>Custom elements have 4 lifecycle methods that can be attached to a component.<\/p>\n\n\n\n<style>\n.wp-block-table table, th, td {\n  border: 1px solid black;\npadding: 10px;\n}\nfigcaption {\nline-height: 18px;\n margin: 10px 0 30px 0;\nfont-size: 12px;\n}\n<\/style>\n\n\n\n<figure class=\"wp-block-table\"><table><tbody><tr><td>connectedCallback<\/td><td>Ran when attached to the DOM<\/td><\/tr><tr><td>disconnectedCallback<\/td><td>Ran when unattached to the DOM<\/td><\/tr><tr><td>attributeChangedCallback<\/td><td>Ran when one of the web component\u2019s attributes is changed. Must explicitly track<\/td><\/tr><tr><td>adoptedCallback<\/td><td>Ran when moved from one HTML document to another<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p> <\/p>\n\n\n\n<p>   <\/p>\n\n\n\n<blockquote class=\"wp-block-quote\">\n<p>While each of them has their uses, we\u2019ll primarily be focusing on the first 3. <code>adoptedCallback<\/code> is primarily useful in niche circumstances and is therefore difficult to make a straightforward demo of.<\/p>\n<\/blockquote>\n\n\n\n<p>Now that we know what the lifecycle methods are, let\u2019s see an example of them in action.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Connection Lifecycles<\/h3>\n\n\n\n<p>The first two lifecycle methods we\u2019ll be talking about are typically used as a pair together: <code>connectedCallback<\/code> and <code>disconnectedCallback<\/code><\/p>\n\n\n\n<p><code>connectedCallback<\/code> is ran when a component is mounted onto the DOM. This means that when you want the element to be shown, you can change your <code>innerHTML<\/code>, add event listeners to elements, or do any other kind of code logic meant to setup your component.<\/p>\n\n\n\n<p>Meanwhile, <code>disconnectedCallback<\/code> is run when the element is being removed from the DOM. This is often used to remove event listeners added during the <code>connectedCallback<\/code>, or do other forms of cleanup required for the element.<\/p>\n\n\n\n<p>Here\u2019s a simple web component that renders a header with the text \u201cHello world\u201d.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-2\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-class\"><span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title\">MyComponent<\/span> <span class=\"hljs-keyword\">extends<\/span> <span class=\"hljs-title\">HTMLElement<\/span> <\/span>{\n  connectedCallback() {\n      <span class=\"hljs-built_in\">console<\/span>.log(<span class=\"hljs-string\">\"I am connecting\"<\/span>);\n      <span class=\"hljs-keyword\">this<\/span>.innerHTML = <span class=\"hljs-string\">`&lt;h1&gt;Hello world&lt;\/h1&gt;`<\/span>;\n  }\n\n  disconnectedCallback() {\n      <span class=\"hljs-built_in\">console<\/span>.log(<span class=\"hljs-string\">\"I am leaving\"<\/span>);\n  }\n}\n\ncustomElements.define(<span class=\"hljs-string\">'my-component'<\/span>, MyComponent);<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-2\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n<div\n\tclass=\"sandbox-embed responsive-embed  sandbox-embed--full-width\"\n\tstyle=\"padding-top: 125%\"\ndata-block-name=\"coderpad-sandbox-embed\">\n\t<iframe src=\"https:\/\/embed.coderpad.io\/sandbox?question_id=181059&#038;use_question_button\" width=\"640\" height=\"800\" loading=\"lazy\" aria-label=\"Try out the CoderPad sandbox\"><\/iframe>\n<\/div>\n\n\n\n<h3 class=\"wp-block-heading\">Attribute Changed<\/h3>\n\n\n\n<p>While there are other methods to pass data to an element (which we\u2019ll touch on shortly), the undeniable simplicity of attributes is hard to deny. They\u2019re widely utilized in HTML-spec tags, and most display custom elements should be able to utilize attributes to pass data from a parent trivially.<\/p>\n\n\n\n<p>While <code>attributeChangedCallback<\/code> is the lifecycle method used to detect when an attribute\u2019s value is changed, you must tell the component which attributes to track.<br><br>For example, in this example we\u2019re tracking the <code>message<\/code> attribute. If the <code>message<\/code> attribute value changes, it will run <code>this.render()<\/code>. However, any other attribute\u2019s value changing will not trigger <code>attributeChangedCallback<\/code> because nothing else is marked to be tracked.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-3\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-class\"><span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title\">MyComponent<\/span> <span class=\"hljs-keyword\">extends<\/span> <span class=\"hljs-title\">HTMLElement<\/span> <\/span>{\n  connectedCallback() {\n      <span class=\"hljs-keyword\">this<\/span>.render();\n  }\n\n   <span class=\"hljs-comment\">\/\/ Could also be:<\/span>\n  <span class=\"hljs-comment\">\/\/ static observedAttributes = &#91;'message'];<\/span>\n  <span class=\"hljs-keyword\">static<\/span> <span class=\"hljs-keyword\">get<\/span> observedAttributes() {\n      <span class=\"hljs-keyword\">return<\/span> &#91;<span class=\"hljs-string\">'message'<\/span>];\n  }\n\n  attributeChangedCallback(name, oldValue, newValue) {\n      <span class=\"hljs-keyword\">this<\/span>.render();\n  }\n\n  render() {\n      <span class=\"hljs-keyword\">const<\/span> message = <span class=\"hljs-keyword\">this<\/span>.attributes.message.value || <span class=\"hljs-string\">'Hello world'<\/span>;\n      <span class=\"hljs-keyword\">this<\/span>.innerHTML = <span class=\"hljs-string\">`&lt;h1&gt;<span class=\"hljs-subst\">${message}<\/span>&lt;\/h1&gt;`<\/span>;\n  }\n}\n\ncustomElements.define(<span class=\"hljs-string\">'my-component'<\/span>, MyComponent);<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-3\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n<div\n\tclass=\"sandbox-embed responsive-embed  sandbox-embed--full-width\"\n\tstyle=\"padding-top: 125%\"\ndata-block-name=\"coderpad-sandbox-embed\">\n\t<iframe src=\"https:\/\/embed.coderpad.io\/sandbox?question_id=181060&#038;use_question_button\" width=\"640\" height=\"800\" loading=\"lazy\" aria-label=\"Try out the CoderPad sandbox\"><\/iframe>\n<\/div>\n\n\n\n<p>You\u2019ll notice that the \u201c<code>attributeChangedCallback<\/code>\u201d receives the name of the attribute changed, it\u2019s previous value, and it\u2019s current value. This is useful for granular manual change detection optimizations.<br><br>However, utilizing attributes to pass values to a component has its limitations. To explain these limitations, we must first start by talking about serializability.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Serializability<\/h2>\n\n\n\n<p>Serialization is the process of turning a data structure or object into a format that can be stored and reconstructed later. A simple example of serialization is using JSON to encode data.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-4\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\">SON.stringify(&#91;\n    {<span class=\"hljs-attr\">hello<\/span>: <span class=\"hljs-number\">1<\/span>},\n    {<span class=\"hljs-attr\">other<\/span>: <span class=\"hljs-number\">2<\/span>}\n])\n\n<span class=\"hljs-comment\">\/\/ \"&#91;{\\\"hello\\\": 1}, {\\\"other\\\":2}]\"<\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-4\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Because this JavaScript object is simple and only utilizes <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Glossary\/Primitive\" target=\"_blank\" rel=\"noopener\">primitive data types<\/a>, it\u2019s relatively trivial to turn into a string. This string can then be saved to a file, sent over HTTP to a server (and back), and be reconstructed when the data is needed again.<\/p>\n\n\n\n<blockquote class=\"wp-block-quote\">\n<p>This simplicity of serialization to JSON is one reason why JSON is such a popular format for transferring data over REST endpoints.<\/p>\n<\/blockquote>\n\n\n\n<h3 class=\"wp-block-heading\">Serializing Limitations<\/h3>\n\n\n\n<p><br>While simple objects and arrays can be serialized relatively trivially, there are limitations. For example, take the following code:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-5\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-keyword\">const<\/span> obj = {\n    method() {\n        <span class=\"hljs-built_in\">console<\/span>.log(<span class=\"hljs-built_in\">window<\/span>);\n    }\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-5\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>While this code\u2019s behavior may seem simple to us reading it as developers, think about it from a machine\u2019s perspective.<br><br>If we wanted to send this object to a server from a client remotely with the method intact, how should we do that?<br><br><code>window<\/code>, while available in the browser, is not available in NodeJS, which the server may likely be written in. Should we attempt to serialize the <code>window<\/code> object and pass it along with the method? What about methods on the <code>window<\/code> object? Should we do the same with those methods?<\/p>\n\n\n\n<p>On the other end of the scale, while <code>console.log<\/code> <strong><em>is<\/em><\/strong> implemented in both NodeJS and browsers alike, it\u2019s implemented using native code in both runtimes. How would we even begin to serialize native methods, even if we wanted to? <em>Maybe <\/em>we could pass machine code? Even ignoring the security concerns, how would we handle the differences in machine code between a user\u2019s ARM device and a server\u2019s x86_64 architecture?<\/p>\n\n\n\n<p><br>All of this becomes a problem before you even consider that your server may well not be running NodeJS. How would you even begin to represent the concept of <code>this<\/code> in a language like Java? How would you handle the differences between a dynamically typed language like JavaScript and C++?<br><\/p>\n\n\n\n<h4 class=\"wp-block-heading\">Let\u2019s Stringify Some Functions<\/h4>\n\n\n\n<p>Now knowing the problems with serializing functions, you may wonder what happens if you run <code>JSON.stringify()<\/code> on <code>obj<\/code>?<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-6\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-keyword\">const<\/span> obj = {\n    method() {\n        <span class=\"hljs-built_in\">console<\/span>.log(<span class=\"hljs-keyword\">this<\/span>, <span class=\"hljs-built_in\">window<\/span>);\n    }\n}\n\n<span class=\"hljs-built_in\">JSON<\/span>.stringify(obj); <span class=\"hljs-comment\">\/\/ \"{}\"<\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-6\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>It simply omits the key from the JSON string. This is important to keep in mind as we go forward.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">HTML Attribute Strings<\/h3>\n\n\n\n<p><br>Why are we talking about serialization in this article? To answer that, I want to mention two truths about HTML elements.<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>HTML attributes are case insensitive<\/li>\n\n\n\n<li>HTML attributes must be strings<\/li>\n<\/ul>\n\n\n\n<p>The first of these truths is simply that for any attribute, you can change the key casing and it will respond the same. According to HTML spec, there is no difference between:<br><\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-7\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml shcb-wrap-lines\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">input<\/span> <span class=\"hljs-attr\">type<\/span>=<span class=\"hljs-string\">\"checkbox\"<\/span>\/&gt;<\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-7\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p><br>And:<br><\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-8\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml shcb-wrap-lines\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">input<\/span> <span class=\"hljs-attr\">tYpE<\/span>=<span class=\"hljs-string\">\"checkbox\"<\/span>\/&gt;<\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-8\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p><br>The second truth is much more relevant to us in this discussion. While it might seem like you can assign non-string values to an attribute, they\u2019re always parsed as strings under-the-hood.<br><br>You might think about being tricky and using JavaScript to assign non-string values to an attribute:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-9\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-keyword\">const<\/span> el = <span class=\"hljs-built_in\">document<\/span>.querySelector(<span class=\"hljs-string\">'input'<\/span>);\nel.setAttribute(<span class=\"hljs-string\">'data-arr'<\/span>, &#91;<span class=\"hljs-number\">1<\/span>, <span class=\"hljs-number\">2<\/span>, <span class=\"hljs-number\">3<\/span>, <span class=\"hljs-number\">4<\/span>]);<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-9\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>However, the attribute\u2019s assigned value may not match your expectations:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-10\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml shcb-wrap-lines\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">input<\/span> <span class=\"hljs-attr\">type<\/span>=<span class=\"hljs-string\">\"checkbox\"<\/span> <span class=\"hljs-attr\">data-arr<\/span>=<span class=\"hljs-string\">\"1,2,3,4\"<\/span>&gt;<\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-10\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>You\u2019ll notice the lack of brackets in the attribute. This is because JavaScript is implicitly running <code>toString<\/code> on your array, which turns it into a string before assigning it to the attribute.<\/p>\n\n\n\n<p>No matter how you spin it &#8211; your attribute will be a string.<\/p>\n\n\n\n<p>This is also why when trying to use attributes for non-string values you may run into otherwise unexpected behavior. This is true even for built-in elements, such as <code>input<\/code>.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-11\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml shcb-wrap-lines\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">input<\/span> <span class=\"hljs-attr\">type<\/span>=<span class=\"hljs-string\">\"checkbox\"<\/span> <span class=\"hljs-attr\">checked<\/span>=<span class=\"hljs-string\">\"false\"<\/span>\/&gt;<\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-11\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Without being aware of this HTML attribute limitation, you may well expect the checkbox to be unchecked. However, when rendered, it appears checked.<\/p>\n\n\n<div\n\tclass=\"sandbox-embed responsive-embed \"\n\tstyle=\"padding-top: 125%\"\ndata-block-name=\"coderpad-sandbox-embed\">\n\t<iframe src=\"https:\/\/embed.coderpad.io\/sandbox?language=html&#038;contents=%3Cinput%20type%3D%22checkbox%22%20checked%3D%22false%22%2F%3E\" width=\"640\" height=\"800\" loading=\"lazy\" aria-label=\"Try out the CoderPad sandbox\"><\/iframe>\n<\/div>\n\n\n\n<p>This is because you\u2019re not passing the boolean <code>false<\/code>, you\u2019re passing the string <code>\"false\"<\/code>, which is (confusingly) truthy.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-12\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-built_in\">console<\/span>.log(<span class=\"hljs-built_in\">Boolean<\/span>(<span class=\"hljs-string\">\"false\"<\/span>)); <span class=\"hljs-comment\">\/\/ true<\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-12\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Some attributes are smart enough to know when you\u2019re intending to assign a number or other primitive value to an element via an attribute, but the implementation internally might look something like:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-13\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-class\"><span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title\">NumValidator<\/span> <span class=\"hljs-keyword\">extends<\/span> <span class=\"hljs-title\">HTMLElement<\/span> <\/span>{\n  connectedCallback() {\n      <span class=\"hljs-keyword\">this<\/span>.render();\n  }\n\n  <span class=\"hljs-keyword\">static<\/span> <span class=\"hljs-keyword\">get<\/span> observedAttributes() {\n      <span class=\"hljs-keyword\">return<\/span> &#91;<span class=\"hljs-string\">'max'<\/span>];\n  }\n\n  attributeChangedCallback(name, oldValue, newValue) {\n      <span class=\"hljs-keyword\">this<\/span>.render();\n  }\n\n  render() {\n      <span class=\"hljs-comment\">\/\/ Coerce \"attribute.value\" to a number. Again, attributes<\/span>\n      <span class=\"hljs-comment\">\/\/ can only be passed as a string<\/span>\n      <span class=\"hljs-keyword\">const<\/span> max = <span class=\"hljs-built_in\">Number<\/span>(<span class=\"hljs-keyword\">this<\/span>.attributes.max.value || <span class=\"hljs-literal\">Infinity<\/span>);\n      <span class=\"hljs-comment\">\/\/ ...<\/span>\n  }\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-13\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>While this tends to be the extent of HTML element\u2019s deserializing of attributes, we can extend this functionality much further.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Pass Array of Strings<\/h2>\n\n\n\n<p>As we touched on shortly, if we simply try to pass an array to an attribute using JavaScript\u2019s <code>setAttribute<\/code>, it will not include the brackets. This is due to <code>Array.toString()<\/code>\u2019s output.<\/p>\n\n\n\n<p>If we attempted to pass the array `<code>[\"test\", \"another\", \"hello\"]<\/code>` from JS to an attribute, the output would look like this:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-14\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml shcb-wrap-lines\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">script<\/span>&gt;<\/span><span class=\"javascript\">\n  <span class=\"hljs-class\"><span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title\">MyComponent<\/span> <span class=\"hljs-keyword\">extends<\/span> <span class=\"hljs-title\">HTMLElement<\/span> <\/span>{\n      connectedCallback() {\n          <span class=\"hljs-keyword\">this<\/span>.render();\n      }\n\n      <span class=\"hljs-keyword\">static<\/span> <span class=\"hljs-keyword\">get<\/span> observedAttributes() {\n          <span class=\"hljs-keyword\">return<\/span> &#91;<span class=\"hljs-string\">'todos'<\/span>];\n      }\n\n      attributeChangedCallback(name, oldValue, newValue) {\n          <span class=\"hljs-keyword\">this<\/span>.render();\n      }\n\n      render() {\n          <span class=\"hljs-keyword\">const<\/span> todos = <span class=\"hljs-keyword\">this<\/span>.attributes.todos.value || <span class=\"hljs-string\">''<\/span>;\n          <span class=\"hljs-keyword\">this<\/span>.innerHTML = <span class=\"hljs-string\">`&lt;p&gt;<span class=\"hljs-subst\">${todos}<\/span>&lt;\/p&gt;`<\/span>;\n      }\n  }\n\n  customElements.define(<span class=\"hljs-string\">'my-component'<\/span>, MyComponent);\n<\/span><span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">script<\/span>&gt;<\/span>\n\n<span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">my-component<\/span> <span class=\"hljs-attr\">id<\/span>=<span class=\"hljs-string\">\"mycomp\"<\/span> <span class=\"hljs-attr\">todos<\/span>=<span class=\"hljs-string\">\"test,another,hello\"<\/span>&gt;<\/span><span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">my-component<\/span>&gt;<\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-14\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n<div\n\tclass=\"sandbox-embed responsive-embed  sandbox-embed--full-width\"\n\tstyle=\"padding-top: 125%\"\ndata-block-name=\"coderpad-sandbox-embed\">\n\t<iframe src=\"https:\/\/embed.coderpad.io\/sandbox?question_id=181062&#038;use_question_button\" width=\"640\" height=\"800\" loading=\"lazy\" aria-label=\"Try out the CoderPad sandbox\"><\/iframe>\n<\/div>\n\n\n\n<p>Because of the output of <code>toString<\/code>, it\u2019s difficult to convert the attribute value back into a string. As such, we only display the data inside of a <code>&lt;p&gt;<\/code> tag. But lists don\u2019t belong in a single paragraph tag! They belong in a <code>ul<\/code> with individual <code>li<\/code>s per item in the list. After all, <a href=\"https:\/\/coderpad.io\/blog\/development\/introduction-to-web-accessibility-a11y\/\">semantic HTML is integral for an accessible website<\/a>!<\/p>\n\n\n\n<p><br>Lets instead use <code>JSON.stringify<\/code> to serialize this data, pass that string to the attribute value, then deserialize that in the element using <code>JSON.parse<\/code>.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-15\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml shcb-wrap-lines\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">script<\/span>&gt;<\/span><span class=\"javascript\">\n  <span class=\"hljs-class\"><span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title\">MyComponent<\/span> <span class=\"hljs-keyword\">extends<\/span> <span class=\"hljs-title\">HTMLElement<\/span> <\/span>{\n      connectedCallback() {\n          <span class=\"hljs-keyword\">this<\/span>.render();\n      }\n\n      <span class=\"hljs-keyword\">static<\/span> <span class=\"hljs-keyword\">get<\/span> observedAttributes() {\n          <span class=\"hljs-keyword\">return<\/span> &#91;<span class=\"hljs-string\">'todos'<\/span>];\n      }\n\n      attributeChangedCallback(name, oldValue, newValue) {\n          <span class=\"hljs-keyword\">this<\/span>.render();\n      }\n\n      render() {\n          <span class=\"hljs-keyword\">const<\/span> todosArr = <span class=\"hljs-built_in\">JSON<\/span>.parse(<span class=\"hljs-keyword\">this<\/span>.attributes.todos.value || <span class=\"hljs-string\">'&#91;]'<\/span>);\n          <span class=\"hljs-built_in\">console<\/span>.log(todosArr);\n          <span class=\"hljs-keyword\">const<\/span> todoEls = todosArr.map(<span class=\"hljs-function\"><span class=\"hljs-params\">todo<\/span> =&gt;<\/span> <span class=\"hljs-string\">`&lt;li&gt;<span class=\"hljs-subst\">${todo}<\/span>&lt;\/li&gt;`<\/span>).join(<span class=\"hljs-string\">'\\n'<\/span>);\n          <span class=\"hljs-keyword\">this<\/span>.innerHTML = <span class=\"hljs-string\">`&lt;ul&gt;<span class=\"hljs-subst\">${todoEls}<\/span>&lt;\/ul&gt;`<\/span>;\n      }\n  }\n\n  customElements.define(<span class=\"hljs-string\">'my-component'<\/span>, MyComponent);\n<\/span><span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">script<\/span>&gt;<\/span>\n\n<span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">my-component<\/span> <span class=\"hljs-attr\">todos<\/span>=<span class=\"hljs-string\">\"&#91;<span class=\"hljs-symbol\">&amp;quot;<\/span>hello<span class=\"hljs-symbol\">&amp;quot;<\/span>,<span class=\"hljs-symbol\">&amp;quot;<\/span>this<span class=\"hljs-symbol\">&amp;quot;<\/span>]\"<\/span>&gt;<\/span>\n<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">my-component<\/span>&gt;<\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-15\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n<div\n\tclass=\"sandbox-embed responsive-embed \"\n\tstyle=\"padding-top: 125%\"\ndata-block-name=\"coderpad-sandbox-embed\">\n\t<iframe src=\"https:\/\/embed.coderpad.io\/sandbox?question_id=181063&#038;use_question_button\" width=\"640\" height=\"800\" loading=\"lazy\" aria-label=\"Try out the CoderPad sandbox\"><\/iframe>\n<\/div>\n\n\n\n<p>Using this method, we\u2019re able to get an array in our <code>render<\/code> method. From there, we simply <code>map<\/code> over that array to create <code>li<\/code> elements, then pass that to our <code>innerHTML<\/code>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Pass Array of Objects<\/h2>\n\n\n\n<p><br>While an array of strings is a straightforward demonstration of serializing attributes, it\u2019s hardly representative of real-world data structures.&nbsp;<\/p>\n\n\n\n<p><br>Let\u2019s start working towards making our data more realistic. A good start might be to turn our array of strings into an array of objects. After all, we want to be able to mark items \u201ccompleted\u201d in a todo app.<\/p>\n\n\n\n<p>For now, we\u2019ll keep it small, and we\u2019ll grow it later. Let\u2019s keep track of the \u201cname\u201d of the todo item, and whether or not it\u2019s been completed:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-16\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-keyword\">const<\/span> data = &#91;{<span class=\"hljs-attr\">name<\/span>: <span class=\"hljs-string\">\"hello\"<\/span>, <span class=\"hljs-attr\">completed<\/span>: <span class=\"hljs-literal\">false<\/span>}];<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-16\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Let\u2019s take a look at how we can display this in a reasonable manner using our custom element:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-17\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml shcb-wrap-lines\"><span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">script<\/span>&gt;<\/span><span class=\"javascript\">\n  <span class=\"hljs-class\"><span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title\">MyComponent<\/span> <span class=\"hljs-keyword\">extends<\/span> <span class=\"hljs-title\">HTMLElement<\/span> <\/span>{\n      connectedCallback() {\n          <span class=\"hljs-keyword\">this<\/span>.render();\n      }\n\n      <span class=\"hljs-keyword\">static<\/span> <span class=\"hljs-keyword\">get<\/span> observedAttributes() {\n          <span class=\"hljs-keyword\">return<\/span> &#91;<span class=\"hljs-string\">'todos'<\/span>];\n      }\n\n      attributeChangedCallback(name, oldValue, newValue) {\n          <span class=\"hljs-keyword\">this<\/span>.render();\n      }\n\n      render() {\n          <span class=\"hljs-keyword\">const<\/span> todosArr = <span class=\"hljs-built_in\">JSON<\/span>.parse(<span class=\"hljs-keyword\">this<\/span>.attributes.todos.value || <span class=\"hljs-string\">'&#91;]'<\/span>);\n          <span class=\"hljs-keyword\">const<\/span> todoEls = todosArr\n              .map(<span class=\"hljs-function\"><span class=\"hljs-params\">todo<\/span> =&gt;<\/span> <span class=\"hljs-string\">`\n              &lt;li&gt;                 \n                &lt;!-- checked=\u201dfalse\u201d doesn\u2019t do what you might think --&gt;\n                &lt;input type=\"checkbox\" <span class=\"hljs-subst\">${todo.completed ? <span class=\"hljs-string\">'checked'<\/span> : <span class=\"hljs-string\">''<\/span>}<\/span>\/&gt;\n                <span class=\"hljs-subst\">${todo.name}<\/span>\n              &lt;\/li&gt;\n          `<\/span>)\n              .join(<span class=\"hljs-string\">'\\n'<\/span>);\n          <span class=\"hljs-keyword\">this<\/span>.innerHTML = <span class=\"hljs-string\">`&lt;ul&gt;<span class=\"hljs-subst\">${todoEls}<\/span>&lt;\/ul&gt;`<\/span>;\n      }\n  }\n\n  customElements.define(<span class=\"hljs-string\">'my-component'<\/span>, MyComponent);\n<\/span><span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">script<\/span>&gt;<\/span>\n\n<span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">my-component<\/span>\n  <span class=\"hljs-attr\">id<\/span>=<span class=\"hljs-string\">\"mycomp\"<\/span>\n  <span class=\"hljs-attr\">todos<\/span>=<span class=\"hljs-string\">\"&#91;{<span class=\"hljs-symbol\">&amp;quot;<\/span>name<span class=\"hljs-symbol\">&amp;quot;<\/span>:<span class=\"hljs-symbol\">&amp;quot;<\/span>hello<span class=\"hljs-symbol\">&amp;quot;<\/span>,<span class=\"hljs-symbol\">&amp;quot;<\/span>completed<span class=\"hljs-symbol\">&amp;quot;<\/span>:false}]\"<\/span>&gt;<\/span>\n<span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">my-component<\/span>&gt;<\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-17\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p><\/p>\n\n\n\n<blockquote class=\"wp-block-quote\">\n<p>Remember, checked=\u201dfalse\u201d will leave a checkbox checked. This is because \u201cfalse\u201d is a truthy string. Reference our \u201cserializing limitations\u201d sections for more reading.<\/p>\n<\/blockquote>\n\n\n\n<p>Now that we\u2019re displaying these checkboxes, let\u2019s add a way to toggle them!&nbsp;<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-18\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-keyword\">var<\/span> todoList = &#91;];\n\n<span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">toggleAll<\/span>(<span class=\"hljs-params\"><\/span>) <\/span>{\n  todoList = todoList.map(<span class=\"hljs-function\"><span class=\"hljs-params\">todo<\/span> =&gt;<\/span> ({...todo, <span class=\"hljs-attr\">completed<\/span>: !todo.completed}));\n  changeElement();\n}\n\n<span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">changeElement<\/span>(<span class=\"hljs-params\"><\/span>) <\/span>{\n  <span class=\"hljs-keyword\">const<\/span> compEl = <span class=\"hljs-built_in\">document<\/span>.querySelector(<span class=\"hljs-string\">'#mycomp'<\/span>);\n  compEl.attributes.todos.value = <span class=\"hljs-built_in\">JSON<\/span>.stringify(todoList);     \n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-18\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p><br>Now, all we need to do is run the function \u201ctoggleAll\u201d on a button press and it will update the checkboxes in our custom element.<\/p>\n\n\n<div\n\tclass=\"sandbox-embed responsive-embed \"\n\tstyle=\"padding-top: 125%\"\ndata-block-name=\"coderpad-sandbox-embed\">\n\t<iframe src=\"https:\/\/embed.coderpad.io\/sandbox?question_id=181065&#038;use_question_button\" width=\"640\" height=\"800\" loading=\"lazy\" aria-label=\"Try out the CoderPad sandbox\"><\/iframe>\n<\/div>\n\n\n\n<p>Now that we have a way to toggle all checkboxes, let\u2019s look at how we can toggle individual todo items.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Pass Objects with Functions<\/h2>\n\n\n\n<p>While there are many ways to have user input in a custom element interact with a parent\u2019s data set, let\u2019s store a method in each todo object and pass it into the custom element.<\/p>\n\n\n\n<p>This pattern follows best practices for components by keeping the data passing unidirectional. In the past, we\u2019ve touched on how to <a href=\"https:\/\/coderpad.io\/blog\/development\/master-react-unidirectional-data-flow\/\">keep your components unidirectional<\/a> for React and Web Components alike.<\/p>\n\n\n\n<p>Let\u2019s change a todo object to reflect something similar:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-19\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\">todoList.push({\n  <span class=\"hljs-attr\">name<\/span>: inputEl.value,\n  <span class=\"hljs-attr\">completed<\/span>: <span class=\"hljs-literal\">false<\/span>,\n  <span class=\"hljs-attr\">id<\/span>: todoId,\n  <span class=\"hljs-attr\">onChange<\/span>: <span class=\"hljs-function\"><span class=\"hljs-params\">()<\/span> =&gt;<\/span> {\n    toggleTodoItem(todoId)\n  }\n});<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-19\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Then, we\u2019ll simply implement our <code>toggleTodoItem<\/code> method using the ID to modify the related todo object:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-20\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">toggleTodoItem<\/span>(<span class=\"hljs-params\">todoId<\/span>) <\/span>{\n  thisTodo = todoList.find(<span class=\"hljs-function\"><span class=\"hljs-params\">todo<\/span> =&gt;<\/span> todo.id == todoId);\n  thisTodo.completed = !thisTodo.completed;\n  changeElement();\n}\n\n<span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">changeElement<\/span>(<span class=\"hljs-params\"><\/span>) <\/span>{\n  <span class=\"hljs-keyword\">const<\/span> compEl = <span class=\"hljs-built_in\">document<\/span>.querySelector(<span class=\"hljs-string\">'#mycomp'<\/span>);\n  compEl.attributes.todos.value = <span class=\"hljs-built_in\">JSON<\/span>.stringify(todoList);\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-20\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>With these changes, we have all of the logic we need from our parent to handle the checkbox logic. Now we need to update our custom element to trigger the <code>onChange<\/code> method when the checkbox is checked. In order to bind an event listener the \u201cinput\u201d element, we need to access the underlying <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/API\/HTMLElement\" target=\"_blank\" rel=\"noopener\">HTMLElement<\/a> reference. To do this, we\u2019ll need to migrate away from the <code>innerHTML<\/code> logic we were using previously in favor of <code>document.createElement<\/code>.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-21\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\">render() {\n  <span class=\"hljs-keyword\">this<\/span>.clear();\n\n  <span class=\"hljs-comment\">\/\/ Create list element<\/span>\n  <span class=\"hljs-keyword\">const<\/span> todosArr = <span class=\"hljs-built_in\">JSON<\/span>.parse(<span class=\"hljs-keyword\">this<\/span>.attributes.todos.value || <span class=\"hljs-string\">'&#91;]'<\/span>);\n  <span class=\"hljs-keyword\">const<\/span> todoEls = todosArr\n      .map(<span class=\"hljs-function\"><span class=\"hljs-params\">todo<\/span> =&gt;<\/span> {\n          <span class=\"hljs-comment\">\/\/ Use `createElement` to get access to the element. We can then add event listeners<\/span>\n          <span class=\"hljs-keyword\">const<\/span> checkboxEl = <span class=\"hljs-built_in\">document<\/span>.createElement(<span class=\"hljs-string\">'input'<\/span>);\n          checkboxEl.type = <span class=\"hljs-string\">\"checkbox\"<\/span>;\n\n          <span class=\"hljs-comment\">\/\/ This doesn't work, we'll explain why shortly<\/span>\n          checkboxEl.addEventListener(<span class=\"hljs-string\">'change'<\/span>, todo.onChange);\n\n          checkboxEl.checked = todo.completed;\n\n          <span class=\"hljs-keyword\">const<\/span> liEl = <span class=\"hljs-built_in\">document<\/span>.createElement(<span class=\"hljs-string\">'li'<\/span>);\n          liEl.append(checkboxEl);\n          liEl.append(todo.name);\n          <span class=\"hljs-keyword\">return<\/span> liEl;\n      });\n\n  <span class=\"hljs-keyword\">const<\/span> ulEl = <span class=\"hljs-built_in\">document<\/span>.createElement(<span class=\"hljs-string\">'ul'<\/span>);\n  <span class=\"hljs-keyword\">for<\/span> (<span class=\"hljs-keyword\">const<\/span> liEl <span class=\"hljs-keyword\">of<\/span> todoEls) {\n      ulEl.append(liEl);\n  }\n\n  <span class=\"hljs-comment\">\/\/ Add header. This should update to tell us how many items are completed<\/span>\n  <span class=\"hljs-keyword\">const<\/span> header = <span class=\"hljs-built_in\">document<\/span>.createElement(<span class=\"hljs-string\">'h1'<\/span>);\n  header.innerText = todosArr.filter(<span class=\"hljs-function\"><span class=\"hljs-params\">todo<\/span> =&gt;<\/span> todo.completed).length;\n\n  <span class=\"hljs-comment\">\/\/ Reconstruct logic<\/span>\n  <span class=\"hljs-keyword\">this<\/span>.append(header);\n  <span class=\"hljs-keyword\">this<\/span>.append(ulEl);\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-21\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Awesome! Now we\u2019ve made all of the changes required, let\u2019s see if it all works together!<\/p>\n\n\n<div\n\tclass=\"sandbox-embed responsive-embed \"\n\tstyle=\"padding-top: 125%\"\ndata-block-name=\"coderpad-sandbox-embed\">\n\t<iframe src=\"https:\/\/embed.coderpad.io\/sandbox?question_id=181066&#038;use_question_button\" width=\"640\" height=\"800\" loading=\"lazy\" aria-label=\"Try out the CoderPad sandbox\"><\/iframe>\n<\/div>\n\n\n\n<p><a href=\"https:\/\/app.coderpad.io\/sandbox?question_id=181066\"><br><\/a>Oh\u2026 Weird\u2026 While our checkboxes seem to be updating, our <code>h1<\/code> is not. What\u2019s more, if we look in our developer console, we don\u2019t see the <code>console.log<\/code>s we would expect to see during a re-render.<\/p>\n\n\n\n<p>Why is that?<\/p>\n\n\n\n<p>Well, as we mentioned in our section about serialization limitations, functions are not serializable. Because of this, when an object with methods are passed to <code>JSON.parse<\/code>, those keys are removed. When we\u2019re adding our event listener, the function is <code>undefined<\/code>, and therefore doesn\u2019t do anything.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-22\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\">checkboxEl.addEventListener(<span class=\"hljs-string\">'change'<\/span>, todo.onChange); <span class=\"hljs-comment\">\/\/ onChange is undefined<\/span><\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-22\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>The checkbox\u2019s state visually updating without being reflected in our data is an example of a misalignment between the DOM and the data we used to build the DOM.<\/p>\n\n\n\n<p>However, we can verify that our code is correct outside of serialization issues. If we change that line of code to utilize the global function <code>toggleTodoItem<\/code> directly, it functions as expected:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-23\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\">checkboxEl.addEventListener(<span class=\"hljs-string\">'change'<\/span>, () =&gt; toggleTodoItem(todo.id))<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-23\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<blockquote class=\"wp-block-quote\">\n<p>Update this line of code in the sandbox above to see the correct behavior!<\/p>\n<\/blockquote>\n\n\n\n<p>While this works for our current setup, one of the advantages of building custom elements is the ability to split out your application to multiple files in order to keep your app\u2019s codebase organized. As soon as <code>toggleTodoItem<\/code> is no longer in the same scope as the custom element, this code will break.<\/p>\n\n\n\n<p>If this isn\u2019t a good long-term solution, what can we do to fix our issue with serialization?<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Pass via Props, not Attributes<\/h2>\n\n\n\n<p>Attributes provide a simple method of passing primitive data to your custom elements. However, as we\u2019ve demonstrated, it falls flat in more complex usage due to the requirement to serialize your data.&nbsp;<\/p>\n\n\n\n<p>Knowing that we\u2019re unable to bypass this limitation using attributes, let\u2019s instead take advantage of JavaScript classes to pass data more directly.<\/p>\n\n\n\n<p>Because our components are classes that extend <code>HTMLElement<\/code>, we\u2019re able to access our properties and methods from our custom element\u2019s parent. Let\u2019s say we want to update <code>todos<\/code> and render once the property is changed.<\/p>\n\n\n\n<p>To do this, we\u2019ll simply add a method to our component\u2019s class called \u201c<code>setTodos<\/code>\u201d. This method will then be accessible when we query for our element using <code>document.querySelector<\/code>.&nbsp;<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-24\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-class\"><span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title\">MyComponent<\/span> <span class=\"hljs-keyword\">extends<\/span> <span class=\"hljs-title\">HTMLElement<\/span> <\/span>{\n  todos = &#91;];\n\n  connectedCallback() {\n      <span class=\"hljs-keyword\">this<\/span>.render();\n  }\n\n  setTodos(todos) {\n      <span class=\"hljs-keyword\">this<\/span>.todos = todos;\n      <span class=\"hljs-keyword\">this<\/span>.clear();\n      <span class=\"hljs-keyword\">this<\/span>.render();\n  }\n\n  render() {\n      <span class=\"hljs-comment\">\/\/ ...<\/span>\n  }\n}\n\n<span class=\"hljs-comment\">\/\/ ...<\/span>\n\n<span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">changeElement<\/span>(<span class=\"hljs-params\"><\/span>) <\/span>{\n  <span class=\"hljs-keyword\">const<\/span> compEl = <span class=\"hljs-built_in\">document<\/span>.querySelector(<span class=\"hljs-string\">'#mycomp'<\/span>);\n  compEl.setTodos(todoList);\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-24\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n<div\n\tclass=\"sandbox-embed responsive-embed  sandbox-embed--full-width\"\n\tstyle=\"padding-top: 125%\"\ndata-block-name=\"coderpad-sandbox-embed\">\n\t<iframe src=\"https:\/\/embed.coderpad.io\/sandbox?question_id=181067&#038;use_question_button\" width=\"640\" height=\"800\" loading=\"lazy\" aria-label=\"Try out the CoderPad sandbox\"><\/iframe>\n<\/div>\n\n\n\n<p>Now, if we toggle items in our todo list, our <code>h1<\/code> tag updates as we would expect: we\u2019ve solved the mismatch between our DOM and our data layer!<\/p>\n\n\n\n<p>Because we\u2019re updating the <em>properties<\/em> of our custom elements, we call this \u201cpassing via properties\u201d, which solves the serialization issues of \u201cpassing via attributes\u201d.<\/p>\n\n\n\n<p>But that\u2019s not all! Properties have a hidden advantage over attributes for data passing as well: memory size.<\/p>\n\n\n\n<p>When we were serializing our todos into attributes, we were duplicating our data. Not only were we keeping the todo list in-memory within our JavaScript, but the browser keeps loaded DOM elements in memory as well. This means that for every todo we added, not only were we keeping a copy in JavaScript, but in the DOM as well (via attribute string).<\/p>\n\n\n\n<p><br>But surely, that\u2019s the only way memory is improved when migrating to properties, right? Wrong!<\/p>\n\n\n\n<p>Because keep in mind, on top of being loaded in-memory in JS in our main <code>script<\/code> tag, and in the browser via the DOM, we were also deserializing it in our custom element as well! This meant that we were keeping a <em>third<\/em> copy of our data initialized in-memory simultaneously!<\/p>\n\n\n\n<p>While these performance considerations might not matter in a demo application, they would add significant complications in production-scale apps.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Conclusion<\/h2>\n\n\n\n<p>We\u2019ve covered a lot today! We\u2019ve introduced some of the core concepts at play with web components, how we\u2019re able to best implement various functionality, and the limitations of the DOM.<\/p>\n\n\n\n<p>While we spoke a lot about passing data by attributes vs. properties today, there are pros and cons to both. Ideally, we would want the best of both worlds: the ability to pass data via property in order to avoid serialization, but keep the simplicity of attributes by reflecting their value alongside the related DOM element.<\/p>\n\n\n\n<p><br>Something else we\u2019ve lost since the start of this article is code readability in element creation. Originally, when we were using <code>innerHTML<\/code>, we were able to see a visual representation of the output DOM. When we needed to add event listeners, however, we were required to switch to <code>document.createElement<\/code>. Preferably, we could attach event listeners without sacrificing the in-code HTML representation of our custom element\u2019s rendered output.<\/p>\n\n\n\n<p>While these features may not be baked into the web component specifications themselves, there are other options available. In our next article, we\u2019ll take a look at a lightweight framework we can utilize to build better web components that can integrate with many other frontend stacks!<\/p>\n","protected":false},"excerpt":{"rendered":"<p>What are web components? How can we build them without relying on a framework?<\/p>\n","protected":false},"author":1,"featured_media":3351,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[9],"tags":[],"persona":[29],"blog-programming-language":[62],"keyword-cluster":[],"class_list":["post-2012","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-development"],"acf":[],"_links":{"self":[{"href":"https:\/\/coderpad.io\/wp-json\/wp\/v2\/posts\/2012","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/coderpad.io\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/coderpad.io\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/coderpad.io\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/coderpad.io\/wp-json\/wp\/v2\/comments?post=2012"}],"version-history":[{"count":54,"href":"https:\/\/coderpad.io\/wp-json\/wp\/v2\/posts\/2012\/revisions"}],"predecessor-version":[{"id":34607,"href":"https:\/\/coderpad.io\/wp-json\/wp\/v2\/posts\/2012\/revisions\/34607"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/coderpad.io\/wp-json\/wp\/v2\/media\/3351"}],"wp:attachment":[{"href":"https:\/\/coderpad.io\/wp-json\/wp\/v2\/media?parent=2012"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/coderpad.io\/wp-json\/wp\/v2\/categories?post=2012"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/coderpad.io\/wp-json\/wp\/v2\/tags?post=2012"},{"taxonomy":"persona","embeddable":true,"href":"https:\/\/coderpad.io\/wp-json\/wp\/v2\/persona?post=2012"},{"taxonomy":"blog-programming-language","embeddable":true,"href":"https:\/\/coderpad.io\/wp-json\/wp\/v2\/blog-programming-language?post=2012"},{"taxonomy":"keyword-cluster","embeddable":true,"href":"https:\/\/coderpad.io\/wp-json\/wp\/v2\/keyword-cluster?post=2012"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}