When we’re loading asynchronous content, it’s advised that you show the user some “skeleton” UI that gives the impression of content being loaded. Let’s use ion-skeleton-text
to show how we’d handle this inside of Ionic!
Inside of an Ionic project we’d traditionally have to accomplish this ourselves with CSS or a third party library. Thankfully, the latest update inside of Ionic 4.1 Hydrogen brings us ion-skeleton-text
which we can use to display skeleton content.
We’ll be looking at this with the context of a Vue.js project, but as Ionic is built with Stencil, the underlying principles are framework-agnostic.
To get started, ensure you have Node.js installed on your machine. Then, run the following in your terminal:
$ npm install -g @vue/cli
$ vue create vue-ion-skeleton
> default project setup
$ npm install @ionic/core @ionic/vue
We then need to set up IonicVue
inside of our project inside of main.js
. We’ll also be importing the basic styles that Ionic requires from @ionic/core
import Vue from 'vue';
import App from './App.vue';
import '@ionic/core/css/core.css';
import '@ionic/core/css/ionic.bundle.css';
import IonicVue from '@ionic/vue';
Vue.use(IonicVue);
Vue.config.productionTip = false;
new Vue({
render : (h) => h(App)
}).$mount('#app');
At this stage, we can test that everything works correctly by creating a bare bones Ionic application inside of App.vue
:
<template>
<ion-app>
<ion-header>
<ion-toolbar color="danger">
<ion-title>S C A R Y</ion-title>
</ion-toolbar>
</ion-header>
<ion-content>
<ion-card>
<img src="https://images.unsplash.com/photo-1513681955987-968b5455d7d7?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=933&q=80"/>
<ion-card-header>
<ion-card-subtitle>Spooky, Scary</ion-card-subtitle>
<ion-card-title>Skeleton</ion-card-title>
</ion-card-header>
<ion-card-content>
Nescio brains an Undead zombies. Sicut malus putrid voodoo horror. Nigh tofth eliv ingdead.
</ion-card-content>
</ion-card>
</ion-content>
</ion-app>
</template>
<script>
export default {
name: 'app',
}
</script>
<style>
#app {
font-family: 'Avenir', Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>
Now that we’ve created a bare bones application, let’s move on to our ion-skeleton-text
example:
For example’s sake, let’s say we wanted to load numerous spooky todos
and each one comes from our API with varying data.
If our user was at a location with bad WiFi (say, a coffee shop like the one I’m in right now), it’d be futile to expect that this data will appear instantaneously.
What do we do? Display skeleton
data of course!
Let’s implement this inside of our application. We’ll use the json-placeholder API to get data from an API and use ion-skeleton-text
as a UI buffer until our data arrives.
<template>
<ion-app>
<ion-header>
<ion-toolbar color="danger">
<ion-title>S C A R Y T O D O S</ion-title>
</ion-toolbar>
</ion-header>
<ion-content>
<ion-list v-if="todos.length > 0">
<ion-item v-for="todo in todos" :key="todo.id">
<ion-label>{{todo.title}}</ion-label>
</ion-item>
</ion-list>
<ion-list v-else>
<ion-item v-for="i in 20" :key="i">
<ion-label>
<ion-skeleton-text animated>
</ion-skeleton-text>
</ion-label>
</ion-item>
</ion-list>
</ion-content>
</ion-app>
</template>
<script>
export default {
name: 'app',
data() {
return {
todos: []
}
},
created() {
setTimeout(
() => (
this.getDataFromAPI()
), 3000)
},
methods: {
async getDataFromAPI() {
try {
const req = await fetch('https://jsonplaceholder.typicode.com/todos')
this.todos = await req.json()
}
catch(e) {
console.error(`Error: ${e}`)
}
}
}
}
</script>
<style>
#app {
font-family: 'Avenir', Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>
Let’s take a deep dive into what’s happening here:
todos
by making a fetch
call to the json-placeholder API. This is done via the getDataFromAPI
method that we’ve created.getDataFromAPI
method inside of the created
hook inside of a setTimeout
with a 3 second delay. This mirrors a moderate speed internet connection and gives us enough time to see our skeleton text in action.ion-skeleton-text
inside of our v-else
block allows us to use the Ionic component that would otherwise replace the skeleton component, thus, keeping as much as the original styling as possible.Note: We’re taking advantage of the animated
attribute within the ion-skeleton-text
to animate this on screen. You’ll see what it looks like when it’s not animated later in this article.
This gives us the following magical piece of UI:
The great thing about the ion-skeleton-text
component is that it’s super flexible! We can use the width
style attribute to change how it looks on screen.
Let’s add a little bit of randomness to our ion-skeleton-text
:
<ion-list v-else>
<ion-item v-for="i in 20" :key="i">
<ion-label>
<ion-skeleton-text :style="`width: ${(80 + Math.random () * Math.floor(240))}px;`">
</ion-skeleton-text>
</ion-label>
</ion-item>
</ion-list>
Tada! 🎉 Now we can make awesome “loading” UIs in places where the spinner doesn’t make sense. It’s a great way to replicate how text-based parts of your components will look white asynchronously loading data.
The source code for this article is available here: Source code
Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.
While we believe that this content benefits our community, we have not yet thoroughly reviewed it. If you have any suggestions for improvements, please let us know by clicking the “report an issue“ button at the bottom of the tutorial.
This textbox defaults to using Markdown to format your answer.
You can type !ref in this text area to quickly search our full set of tutorials, documentation & marketplace offerings and insert the link!
Sign up for Infrastructure as a Newsletter.
Working on improving health and education, reducing inequality, and spurring economic growth? We'd like to help.
Get paid to write technical tutorials and select a tech-focused charity to receive a matching donation.