It's just about Halloween so now seems to be the perfect time to discuss a technique I've started using more lately: skeleton screens.
Skeleton Screens
If you've used just about any major website in the last couple of years you've probably seen skeleton screen. I particularly like them because they give you an idea of what to expect before the data loads. With a mobile device, they make extra sense to me since at times the user's data connection could be particularly slow.
NativeScript
If you aren't familiar with NativeScript, you should really check it out. NativeScript is my favorite mobile development framework, between having 100% access to Native API's, Angular support, and their online playground where you can write code in your browser and test it on your phone. These features and more make it's the hands down the best way to develop multi-platform apps.
ListView Skeletons in NativeScript
Normally we could approach this with a ngIf
and a else
, something like:
<StackLayout *ngIf="data$ | async; else skeletons">
<!---View of data after Loaded -->
</StackLayout>
<ng-template #skeletons>
<!--- Skeletons Here -->
</ng-template>
This way before our observable with my response object emits a value it will display a different template. This pattern works pretty well for both NativeScript and the Web (replace StackLayout
with div
).
However, for this use case, we have a long list of items that we are displaying so it is best to use a ListView for performance reasons. Because we want our skeletons to mimic the UI as much as possible we will want them in a ListView as well.
If we wanted we could use the same pattern as above and have two separate ListViews on my page. One for the content, one for my skeletons. This is not a very efficient use of our layout and will make it harder to make changes to the UI since it will be decoupling the skeletons from the end result.
ListView Template Selectors
NativeScript's ListView has an easy way to alternate between templates. By defining a itemTemplateSelector
callback function and giving each template a key with the nsTemplateKey
attribute, we are able to swap layouts. Every time a ListView loads an item to display to the user, the template selector function fires and determines which template to use for that item. The last thing we need to do is then pass our template selector callback to our ListView via the input.
<ListView [items]="genres$ | async" [itemTemplateSelector]="templateSelector">
<ng-template nsTemplateKey="template" let-item="item" let-index="index">
<GridLayout class="music-genre" rows="*" columns="55,*">
<Label row="0" col="0" [text]="index + 1" class="number"></Label>
<Label row="0" col="1" [text]="item" class="genre"></Label>
</GridLayout>
</ng-template>
<ng-template nsTemplateKey="skeleton">
<GridLayout class="music-genre skeleton" rows="*" columns="55,*">
<Label row="0" col="0" text=" " class="number number-skeleton"></Label>
<Label row="0" col="1" text=" " class="genre genre-skeleton"></Label>
</GridLayout>
</ng-template>
</ListView>
Our NativeScript Angular Template with a ListView to switch between a skeleton and normal template
A itemTemplateSelector
callback function has 3 parameters, the current item, that items index, and all the items currently in the list.
templateSelector(item: any, index: number, items: any[]) {
return 'template';
};
Template selector function that always returns the type of template
RxJS startWith Operator
With the above template selector, we sill only see the genre items. We will need a way to load an array of "skeletons" first, then when we get our response back from the server replace the skeleton array with the results data array. This is where RxJS's startWith
operator comes into play. What we need to do is pass an array of objects to the pipeable operator startsWith
. We then "pipe" this operator when we set the genres$
observable property in the components ngOnInit
function. Now when we subscribe to the genres$
observable it will emit two values. First the array of skeletons, then the results of our HTTP request, when it completes.
What I like to do is declare a ISkeleton
interface which is an object that has a boolean property on it called skeleton
. Then create an array of objects each with there own skeleton
property set to true. This is then the array that is passed to the startsWith
operator to be emitted first by the genres$
observable.
interface ISkeleton{
skeleton: boolean;
}
const SKELETONS: ISkeleton[] = [
{ skeleton: true },
{ skeleton: true }
... // since this is for a listView I have quite a few of these.
];
@Component({...})
export class HomeComponent implements OnInit {
genres$: Observable<ISkeleton[] | string[]>;
constructor(private genreService: GenreService) {
}
ngOnInit() {
// Get a list of genre's from the genre service. but first, start by
// emmiting the SKELETONS array.
this.genres$ = this.genreService.getGenres()
.pipe(startWith(SKELETONS));
}
Now everything is just about hooked up. However, if we run our app we are still not seeing our skeleton templates. The last step is to update our template selector function. All we need to do is check if the item
object that is passed to it has a skeletons property on it. If that returns truthy, then have our template selector function return the key of 'skeleton'.
templateSelector(item) {
if (item.skeleton) {
return 'skeleton';
}
return 'template';
};
Finished Product
As you can see in the above gif when a user first comes to this view they are greeted with a list of skeletons immediately. Then once the HTTP request completes the view switches to a list of musical genres. We have been primed so to speak for this layout with the skeletons.
Setting up our skeleton screens this way is only the beginning, there is so much more you could do with this pattern especially when it comes to animations. Because of the power of NativeScript's ListView, Angular and RxJS we have a very succinct code base to build upon.
You can view a working sample on the NativeScript Playground here. Just a disclaimer it's currently styled to look good on iOS since I don't have an Android phone to test on currently.
Thanks for reading, I hope you enjoyed my take on this pattern. If you did, please share with your friends and co-workers. I'd really enjoy any feedback you have via the comments below. Or if you have your own take on skeleton screens in NativeScript let's see those too!
Top comments (4)
Just awesome!! Willing to get into NativeScript for all comming mobile projects.
Glad you enjoy it! Feel free to hit me up if you need any help!
Beautiful! Thanks for sharing this technique :)
Thank you! We need more NativeScript articles! :-)