Yesterday, I had an interesting chat with a friend about how I would share state between Web Components, and get them to talk to each other in a web app.
Today, let's explore what that might look like!
An example I like to use for this kind of thing is an ecommerce platform with a few interconnected components.
You might have a <cart-link>
Web Component that displays a link to the checkout page, with the number of items in the cart as it's text.
<cart-link> <a href="/checkout">🛒 2 Items</a> </cart-link>
You might also have a <product-listing>
Web Component that displays an Add to Cart button if the item isn't in your text, or a message if it is.
<!-- Not in the cart --> <product-listing price="39" uid="tshirt-jolly-roger"> <button>Add to Cart - $39</button> </product-listing> <!-- Already in the cart --> <product-listing price="39" uid="tshirt-jolly-roger"> <em>This is item in your cart</em> </product-listing>
And powering both of these Web Components is a cart
variable—an object ({}
)—that holds the items currently in the cart.
Whenever an item is added to the cart
, the <cart-link>
element needs to update the text about how many items are in the cart. The <product-listing>
also needs to update itself to show either a <button>
or message depending on whether the item is in the cart.
Let's take a look at how to wire up all of these pieces so they can talk to each other.
A simple signal
To make this work, we'll create a Signal
class that will actually hold the cart
data.
Whenever the data is updated, the Signal
will emit a custom event that the other Web Components can listen to. We'll start by storing whatever value is passed in to the this.value
property.
class Signal { // Initialize the class constructor (val) { this.value = val; } }
We'll create a new Signal()
for the cart
like this…
let cart = new Signal({});
Next, we'll add an add()
method that accepts a key
and val
as arguments.
The method will add or update the key
in the this.value
object. Then, it will emit a custom cart-updated
event.
Note: In a real production app, I'd either use setter and getter methods or Proxies for this. But this is a useful, simple model for teaching.
class Signal { // Initialize the class constructor (val) { this.value = val; } // Add a new item add (key, val) { // Update the value this.value[key] = val; // Create a new event let event = new CustomEvent('cart-updated', { bubbles: true, detail: {key, val} }); // Dispatch the event return document.dispatchEvent(event); } }
While we're here, let's also add a size()
method that returns the number of items in the this.value
object, and a has()
method that checks if a key
is in the this.value
object or not.
class Signal { // ... // Get the number of items in the cart size () { return Object.keys(this.value).length; } // Check if an item is in the object has (key) { return !!this.value[key]; } }
Creating the <cart-link>
component
For the <cart-link>
component, we'll create a render()
method that gets the cart.size()
, and displays it as a link inside the custom element.
customElements.define('cart-link', class extends HTMLElement { // Instantiate the Web Component constructor () { // Inherit parent class properties super(); // Render the initial UI this.render(); } // Render the UI render () { this.innerHTML = `<a href="/checkout">🛒 ${cart.size()} Items</a>`; } });
We'll add an event listener for the cart-updated
event, and use the handleEvent()
method to run the render()
method whenever the cart
is updated.
customElements.define('cart-link', class extends HTMLElement { // Instantiate the Web Component constructor () { // Inherit parent class properties super(); // Render the initial UI this.render(); // Listen for cart events document.addEventListener('cart-updated', this); } // Handle Events handleEvent (event) { this.render(); } // Render the UI render () { this.innerHTML = `<a href="/checkout">🛒 ${cart.size()} Items</a>`; } });
Now, whenever the cart
is updated, the <cart-link>
gets updated automatically.
🧐 Want to learn more about Web Components? I'm adding all of my guides around building Web Components to my members-only toolkit. Joining is a great way to support my work!
Creating the <product-listing>
component
We'll do something similar for the <product-listing>
Web Component.
When the Web Component instantiates, we'll use the Element.getAttribute()
method to get the [uid]
and [price]
attributes, and save them to properties.
Then well run a render()
method to render the UI.
customElements.define('product-listing', class extends HTMLElement { /** * Instantiate the Web Component */ constructor () { // Inherit parent class properties super(); // Set properties this.uid = this.getAttribute('uid'); this.price = parseFloat(this.getAttribute('price')); // Render the initial UI this.render(); } });
Inside the render()
method, we'll use the cart.has()
method to check if this.uid
is in the cart
already.
If it is, we'll show a message. If not, we'll show a button to add the item to the cart, with this.price
as part of the button text.
// Render the UI render () { this.innerHTML = cart.has(this.uid) ? '<em>This is item in your cart</em>' : `<button>Add to Cart - $${this.price}</button>`; }
Next, we'll add an event listener for the cart-updated
event. We'll also listen for click
events inside the custom element.
/** * Instantiate the Web Component */ constructor () { // Inherit parent class properties super(); // Set properties this.uid = this.getAttribute('uid'); this.price = parseFloat(this.getAttribute('price')); // Render the initial UI this.render(); // Listen for events document.addEventListener('cart-updated', this); this.addEventListener('click', this); }
Inside the handleEvent()
method, we'll check if the event.type
is cart-updated
.
If so, we'll run the render()
method to update the UI, and return
to end the function. If the event.type
is click
, and the event.target
(the clicked element) is or is inside a button
, we'll run the cart.add()
method to add this.uid
to the cart
object.
// Handle events handleEvent (event) { // If it's a cart update event if (event.type === 'cart-updated') { this.render(); return; } // If it's a click on the button if (event.type === 'click' && event.target.closest('button')) { cart.add(this.uid, this.price); } }
Adding the item will trigger the cart-updated
event, which will trigger a render of the UI.
Putting it all together
Here's a demo you can use to play around with this idea. And if you'd prefer, here's the downloadable source code on GitHub.
Depending on the use case, there are other ways you might handle this kind of thing. But this would probably be my starting point for an app like this.
Like this? A Go Make Things membership is the best way to support my work and help me create more free content.
Cheers,
Chris
Want to share this with others or read it later? View it in a browser.
0 Komentar untuk "[Go Make Things] How would you share state between Web Components?"