Faster NativeScript ListView with Multiple Item Templates

In this post we are going to look into how to optimize the performance of NativeScript ListView using multiple item templates. The code examples are targeting NativeScript with Angular, but the same ideas apply, if you are using NativeScript without Angular.

Background

Under the hood NativeScript uses the native list controls to render ListView. The smooth as butter scrolling in these controls depends on two major features:

  • UI Virtualization — Views are created only for the items that are currently visible. Fewer elements in memory => less memory footprint.
  • View Recycling — Whenever an item scrolls out of the view port, the view that was rendering it is not destroyed. It is put in a pool of recycled views and reused when an new item scrolls into view.
Image for post
Image for post
Virtualization (left) and Recycling(right)

Rendering Different Items

Sometimes we need to render collection with different items and we need to use different templates for them. Let’s say we are showing a collection of news items. Some items are big, some small, and some of the small ones have images and some don’t.

Using Single Template

If you have been using angular for while, you might be tempted to apply some ngIf/ngSwitch magic. You will probably end up with something like this:

<ListView [items]="items">
<ng-template let-item="item">
<StackLayout>
<GridLayout *ngIf="item.type === 'big'">
<!-- big item template -->
</GridLayout>
<GridLayout *ngIf="item.type === 'small' && item.imageUrl">
<!-- small item with image -->
</GridLayout>
<GridLayout *ngIf="item.type === 'small' && !item.imageUrl">
<!-- small item with no image -->
</GridLayout>
</StackLayout>
</ng-template>
</ListView>
Image for post
Image for post

What happens here is that recycling doesn’t work as intended anymore.

Let’s say a big item scrolls out of view and its view template is put into the recycle pool and then reused as small-image item. The angular renderer will have to create all the views contained in the second ngIf and will destroy all the views form the first ngIf. The only reused part will be the wrapping StackLayout (which is not really needed anyway).

This means this will be slow and generate garbage at the same time. Here is how this looks like:

That was slow! We can clearly see the GC kicking in and freezing the scroll.

The Better Approach

Luckily there is a cure. There is a way to instruct the ListView to use different item templates based on the item criteria you define. The good part is that it will keep the recycled views in different pools and will recycle a view form the right pool when needed to render the next item. The only thing that will have to change are the bindings — no creating/destroying views.

Image for post
Image for post

No need for ngIf and therefore - no excessive creating/destroying of UI Views each time a view is recycled. As a bonus, we can get rid of the StackLayout that was previously just holding the 3 different templates.

And here is the code for that:

<ListView [items]="items" [itemTemplateSelector]="templateSelector">
<ng-template nsTemplateKey="big" let-item="item">
<!-- big item template -->
</ng-template>
<ng-template nsTemplateKey="small" let-item="item">
<!-- small item with image -->
</ng-template>
<ng-template nsTemplateKey="small-no-image" let-item="item">
<!-- small item with no image -->
</ng-template>
</ListView>

In the markup we define 3 different <template> elements giving each one a name using nsTemplateKey directive. We are also giving the ListView a itemTemplateSelector function that is defined in the component code. It should return the name of the template to be used given the actual item:

public templateSelector(item: NewsItem, index: number, items: NewsItem[]) {
if (item.type === "big") {
return "big"
}

if (item.type === "small" && item.imageUrl) {
return "small";
}
if (item.type === "small" && item.imageUrl) {
return "small-no-image";
}

throw new Error("Unrecognized template!")
}

Let’s see how this behaves:

Neat! This is the performance we are looking for!

Conclusion

Here is the code for both super slow *ngIf template and for the ultra fast template selector. Final thougths:

  • ngIf in ListView item templates: 👎🐌👎
  • Multiple templates + template selector: 👍🚀👍

Written by

NativeScript core member. Co-organizer of Angular Sofia. Father of a (future) rockstar.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store