Ionic 4.1 & Vue.js: Skeleton Text

Paul Halliday

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.

Ionic Vue + Skeleton Text

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

main.js

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:

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>

Scary, Spooky, Ionics.

Now that we’ve created a bare bones application, let’s move on to our ion-skeleton-text example:

Skeleton UI

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:

  1. Firstly, we’re getting access to a list of todos by making a fetch call to the json-placeholder API. This is done via the getDataFromAPI method that we’ve created.
  2. We’re then calling the 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.
  3. The use of 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:

Results of our Ionic Skeleton project

Other Examples

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>

Results of our Ionic Skeleton project with randomness

Summary

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

  Tweet It

🕵 Search Results

🔎 Searching...

Sponsored by #native_company# — Learn More
#native_title# #native_desc#
#native_cta#