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.
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>

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.

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: 👎🐌👎

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

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