Using Web Components in Alpine.js
source link: https://www.raymondcamden.com/2023/06/02/using-web-components-in-alpine
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
To be honest, the TLDR for this entire post is, "It just works", so I'd more than understand if you stop reading, but like most things in my life, I like to see it working to reassure myself of the fact. So with that out of the way, let's consider a simple example.
First Attempt #
I began by defining a super simple Alpine application that just has a list of cats:
document.addEventListener('alpine:init', () => {
Alpine.data('app', () => ({
cats:[
{name:"Luna", age:11},
{name:"Pig", age:9},
{name:"Elise", age:13},
{name:"Zelda", age:1},
{name:"Grace", age:12},
]
}))
});
In the HTML, I iterate over each cat and display it with a web component I'll define in a moment:
<div x-data="app">
<template x-for="cat in cats">
<p>
cat: <cat-view :name="cat.name" :age="cat.age"></cat-view>
</p>
</template>
</div>
As the component hasn't been defined yet, all I'll see are 5 "cat:" messages:
Alright, let's define our web component:
class CatView extends HTMLElement {
constructor() {
super();
this.name = '';
this.age = '';
}
connectedCallback() {
if(this.hasAttribute('name')) this.name = this.getAttribute('name');
if(this.hasAttribute('age')) this.age = this.getAttribute('age');
this.render();
}
render() {
this.innerHTML = `
<div>
I'm a cat named ${this.name} that is ${this.age} years old.
</div>
`;
}
}
if(!customElements.get('cat-view')) customElements.define('cat-view', CatView);
All this component is doing is picking up the name
and age
attributes and rendering it out in a div
. Let's see what this renders:
So what happened? Alpine successfully added the components to the DOM, but the attributes were updated after the connectedCallback
event was fired. This was - I think - expected - and luckily is simple enough to fix with observedAttributes
and attributeChangedCallback
:
class CatView extends HTMLElement {
constructor() {
super();
this.name = '';
this.age = '';
}
connectedCallback() {
if(this.hasAttribute('name')) this.name = this.getAttribute('name');
if(this.hasAttribute('age')) this.age = this.getAttribute('age');
this.render();
}
render() {
this.innerHTML = `
<div>
I'm a cat named ${this.name} that is ${this.age} years old.
</div>
`;
}
static get observedAttributes() { return ['name', 'age']; }
attributeChangedCallback(name, oldValue, newValue) {
this[name] = newValue;
this.render();
}
}
And voila, you can see the result below:
Small Update #
Cool, so that worked, but I wanted to be sure that updating data in Alpine worked, so I added a quick button:
<button @click="addCat">Add Cat</button>
This was tied to this handler:
addCat() {
let newCat = {
name:`New cat ${this.cats.length+1}`,
age: this.cats.length
};
this.cats.push(newCat);
}
I'm just giving a name and age based on the number of cats already in the data set. Again, no surprises here, but it works as expected:
You can find this version below. I encourage you to hint that "Add Cat" button multiple times because more cats is always a good thing.
Photo by Christopher Alvarenga on Unsplash
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK