Skip to content

Instantly share code, notes, and snippets.

@richard-flosi
Last active January 17, 2024 07:58
Show Gist options
  • Star 46 You must be signed in to star a gist
  • Fork 7 You must be signed in to fork a gist
  • Save richard-flosi/b6cdba782576447fcc9789f6cdfe2e31 to your computer and use it in GitHub Desktop.
Save richard-flosi/b6cdba782576447fcc9789f6cdfe2e31 to your computer and use it in GitHub Desktop.
Web Component using Custom Element, Shadow DOM, fetch, async/await, and the Star Wars API
<html>
<head>
<script>
customElements.define("star-wars-planets", class extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: "open" });
}
static get observedAttributes() { return ["loading", "planets"]; }
get loading() {
return JSON.parse(this.getAttribute("loading"));
}
set loading(v) {
this.setAttribute("loading", JSON.stringify(v));
}
get planets() {
return JSON.parse(this.getAttribute("planets"));
}
set planets(v) {
this.setAttribute("planets", JSON.stringify(v));
}
async fetchPlanets(url) {
this.loading = true;
const response = await fetch(url);
const json = await response.json();
this.planets = json;
this.loading = false;
}
async connectedCallback() {
this.shadowRoot.addEventListener("click", (event) => {
const name = event.srcElement.id;
if (this[name]) {
this[name]();
}
});
await this.fetchPlanets("https://swapi.co/api/planets");
}
disconnectedCallback() {
}
attributeChangedCallback(attrName, oldVal, newVal) {
this.render();
}
next() {
this.fetchPlanets(this.planets.next);
}
previous() {
this.fetchPlanets(this.planets.previous);
}
renderPrevious() {
if (this.planets.previous) {
return `<div id="previous">Previous</div>`;
} else {
return `<div>No previous planets.</div>`;
}
}
renderNext() {
if (this.planets.next) {
return `<div id="next">Next</div>`;
} else {
return `<div>No more planets.</div>`;
}
}
render() {
if (this.loading) {
this.shadowRoot.innerHTML = `Loading...`;
} else {
this.shadowRoot.innerHTML = `
<span>
<h3><slot name="title">Star Wars Planets</slot></h3>
<div>Count: ${this.planets.count}</div>
${this.renderPrevious()}
${this.renderNext()}
<table>
<tr>
<th>Name</th>
<th>Terrain</th>
<th>Population</th>
<th>Climate</th>
<th>Diameter</th>
<th>Gravity</th>
<th>Orbital Period</th>
<th>Rotation Period</th>
<th>Surface Water</th>
<th>URL</th>
</tr>
${this.planets.results.map((planet) => {
return `
<tr>
<td>${planet.name}</td>
<td>${planet.terrain}</td>
<td>${planet.population}</td>
<td>${planet.climate}</td>
<td>${planet.diameter}</td>
<td>${planet.gravity}</td>
<td>${planet.orbital_period}</td>
<td>${planet.rotation_period}</td>
<td>${planet.surface_water}</td>
<td>${planet.url}</td>
</tr>
`;
}).join("")}
</table>
</span>
`;
}
}
});
</script>
</head>
<body>
<star-wars-planets>
<span slot="title">My Custom Star Wars</span>
</star-wars-planets>
</body>
</html>
@mudroljub
Copy link

A great example man!

@emiremiroglu
Copy link

That's the example I was looking for! Thanks!

@wwestrop
Copy link

wwestrop commented Dec 9, 2019

This may be the only example on the web of connectedCallback being used asynchronously, helped me massively! 👍

@richard-flosi
Copy link
Author

Cool. Glad this was useful. I also have a repository with more code here: https://github.com/richard-flosi/star-wars-planets

@wwestrop
Copy link

wwestrop commented Dec 9, 2019

Awesome! 😀

@verticalgrain
Copy link

Thanks for the excellent example!

@madamerobot
Copy link

Great example, exactly what I was looking for. Thanks for sharing. 👍🏼

@danielsimao
Copy link

Thanks a lot!!! ✌️

@roadev
Copy link

roadev commented Feb 24, 2021

Thank you so much!!!

@dixonnixon
Copy link

how to deal with if I want to do thomething after async connectedCallback? PLEASE help)!!!!

@richard-flosi
Copy link
Author

@dixonnixon add any event listeners you need in connectedCallback and handle attribute changes in attributionChangedCallback. attributeChangeCallback will be called with the attribution name that changed, the old value, and the new value.

@dixonnixon
Copy link

@richard-flosi, huh, I decided to use another class as a View to hold and store event listeners

@Juuro
Copy link

Juuro commented Apr 10, 2022

Great, thank you! That use of async connectedCallback() was very helpful! 🙂

@mattbontrager
Copy link

This is some very nice work. Simple, clean implementation that makes the intention of each feature easily understandable.

@richard-flosi
Copy link
Author

@mattbontrager thanks! I'm glad this is still useful. I have a related blog post that is more recent which may provide additional information.
Check out: Learn the Lifecycle of a Web Component by Building a Custom Element as well.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment