Using skeletons in Angular

Using skeletons in Angular

In the last post, we learned how to use resolvers to load asynchronous content in a simple application. Now, let's use the same example. But, working with skeletons.

Wait a second. Skeletons?

Yeah, skeletons, it's just a feature that we can implement to show a preview of content before the real content loads. This content is commonly a grey section, which simulates the shape of the real content on the page. Let's see an example.

As you can tell, YouTube uses a skeleton when the real video data is loading.

Now, let's implement this feature in our articles project. Firstly, let's review the current behavior when we go to the localhost:4200/fake-page route.

As you can see, there is a moment while the data is being resolved from the API (in this case I just use a setTimeout to simulate the backend delay) where the page doesn't show anything. Consequently, we are creating a bad experience for the user.

Implementing skeletons

To implement the skeletons in an application, we should be aware of what the real content looks like. Next, we create a structure of div or other HTML tags that fits with the real content.

Let's do it.

In the fake-page.component.html let's add the skeleton section after the real content:

1<!-- Real content -->
2<section class="container">
3  <h1>{{articleContent.title}}</h1>
4  <article class="article">
5    {{articleContent.content}}
6  </article>
7  <p class="author">- by {{articleContent.author}}</p>
8</section>
9
10<!-- Skeleton content -->
11<section class="container skeleton">
12  <div class="title-skeleton"></div>
13  <div class="content">
14    <div class="text-skeleton"></div>
15    <div class="author-skeleton"></div>
16  </div>
17</section>
18

In this code, we just replace the <h1>,<article> & <p> tags for <div> tags. Meanwhile, in the fake-page.component.scss code:

1.skeleton {
2  .title-skeleton {
3    width: 214px;
4    height: 25px;
5    margin-bottom: 30px;
6    margin-top: 30px;
7    border-radius: 4px;
8    background-color: #EEEEF0;
9  }
10  
11  .content {
12    display: flex;
13    flex-direction: column;
14    align-items: flex-end;
15    
16    .text-skeleton {
17      width: 500px;
18      height: 15px;
19      background-color: #EEEEF0;
20      border-radius: 4px;
21      margin: 4px 0 4px 0;
22    }
23    
24    .author-skeleton {
25      border-radius: 4px;
26      width: 130px;
27      height: 15px;
28      margin-top: 20px;
29      background-color: #EEEEF0;
30    }
31  }
32}
33

In this example, we are just converting div boxes into skeleton boxes using static width & height properties. But, feel free to add your own values in %, rem.

With this code, the skeleton should look something like this:

And, let's create an array in the fake-page.component.ts component to simulate multiple lines in the content.

1skeletonContent: Array<any> = Array.from({length: 21});
2

And in the skeleton HTML let's update the class='text-skeleton' div

1<div class="text-skeleton" *ngFor="let item of skeletonContent">
2</div>
3

Now, if we compare the real content with the skeleton, we get:

Finally, let's add some logic to decide if we load the real content or the skeleton. Remember that we show the skeleton when we don't have the backend response yet. Then, when we got the data, we should hide the skeleton and show the real content.

In the fake-page.component.ts let's add a getter:

1get hasContent() {
2  const { 
3    title,
4    content,
5    author
6  } = this.articleContent;
7  return (title === '' || content === '' || author === '') ? false : true;
8}
9

This method reviews if the backend data is loaded or not yet.

Now, in the fake-page.component.html file:

1<section class="container" *ngIf="hasContent; else skeleton">
2  <h1>{{articleContent.title}}</h1>
3  <article class="article">
4    {{articleContent.content}}
5  </article>
6  <p class="author">- by {{articleContent.author}}</p>
7</section>
8
9<ng-template #skeleton>
10  <section class="container skeleton">
11    <div class="title-skeleton"></div>
12    <div class="content">
13      <div class="text-skeleton" *ngFor="let item of skeletonContent"></div>
14      <div class="author-skeleton"></div>
15    </div>
16  </section>
17</ng-template>
18
19

In line 1, we add *ngIf="hasContent; else skeleton" an if-else condition. If there is content, show the real content section, else show the skeleton section.

The final feature should look like this:

Summary

We learned how to implement skeletons in a simple application, using div as a display block. You can find the full code here.