DEV Community

Josh Sommer
Josh Sommer

Posted on

NativeScript ListView Skeleton Screens with Angular & RxJS ☠️

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.

Skeletons being used on Slack
Skeletons being used on Slack.

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.

Screenshot of my ListView

Screenshot of my ListView

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

All tied together

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!

Latest comments (4)

Collapse
 
danielblazquez profile image
Daniel G. Blázquez

Thank you! We need more NativeScript articles! :-)

Collapse
 
sebastiandg7 profile image
Sebastián Duque G • Edited

Just awesome!! Willing to get into NativeScript for all comming mobile projects.

Collapse
 
_joshsommer profile image
Josh Sommer

Glad you enjoy it! Feel free to hit me up if you need any help!

Collapse
 
shiv19 profile image
Shiva Prasad

Beautiful! Thanks for sharing this technique :)