Advanced Angular Structural Directive to Render Long Lists
This article shows you how to create an advanced structural directive similar to Angular’s own ngFor and use that to render a long list using progressive rendering concept.
Pre-requisites
Basic understanding of JavaScript and Angular.
Setup and brief structural directives refresher.
We plan to use this directive like this in our components.
<ul>
<li *ngxtFor="
let item of longList;
itemsAtOnce: 500;
intervalLength: 50">
Item - {{item.id}}
</li>
</ul>
The “*” syntax in the directive basically tells angular to wrap the element in an ngTemplate and not render it right away. We can then access the template in our directive by injecting TemplateRef
and ViewContainer
services and use our custom logic to render the template.
itemsAtOnce
and intervalLength
will be inputs specify how many items it should render at a time and how long should it wait before rendering it.
Next, in our app component we create a list of 50000 items.
ngOnInit() {
const longList = [];
for (let i = 0; i < 50000; i++) {
longList.push({ id: i });
}
this.longList = longList;
}
That will be all for our app component. Next let’s start creating our directive. We will start with basic structure without any logic first.
The of
in the ngxtForOf
input lets us use the let items of items
syntax with the directive. The other inputs also have to be prefixed with the selector of the directive for them to be used in the syntax shown in the first snippet.
If you are completely new to structural directives, you can refer to the angular guide on structural directives here
Introducing IterableDiffers angular service.
At this point we could just take the items and render them using viewContainer.createEmbeddedView
method in the ngOnInit
handler but that would not work if items get added to the list or if they get removed from it. That is where IterableDiffers
comes into picture. It provides us a diffing strategy using which we can handle scenarios when items get added to or removed from our list without having to write the logic ourselves. So let’s add that to our directive.
As seen above, when we receive the items, we are creating an instance of IterableDiffer.
This is what we will use to find when items are added/removed to the list.
IterableDiffers.find(items)
will check if Angular has an IterableFactory
which provides diffing strategy for the items which were passed to it. In our case, items
is an iterable and Angular does have a factory for that. So that will be returned. Then we call create
on that factory to get the instance of the IterableDiffer
which can be used to diff the list.
Important thing to note is that forEachAddedItem
and forEachRemovedItem
iterate over the IterableRecord
objects. These hold a lot of additional information in addition to list item itself. We will use the currentIndex
property to ensure we are adding/removing the template references from the correct index.
For learning more about IterableDiffers
in Angular, refer to this wonderful article
Final step: Add the logic to progressively render the list.
Now we have all the pieces together. The progressiveRender
method takes the array of IterableChangeRecord
objects and passes it on to the renderItems
method fixed number items at a time depending on the inputs. This method creates them on the DOM using createEmbeddedView
method which you might have already seen before in simple structural directives.
Note the third argument to createEmbeddedView
. This index
argument ensures items are rendered in their correct spot when list items are removed or replaced.
The viewRefsMap
is a private map which the directive uses to store references to the embedded view objects. It is populated as and when list items are processed. This map is then used in ngDoCheck
to remove the view items when they are removed from the list from our app component
See this directive in action on stackblitz.
You can see the difference in performance when rendering a large list using our custom directive and the built-in ngFor
directive.
References
I took the progressive rendering idea from this article by Giancarlo Buomprisco
Thanks to Yoni Amishav for his great article on how to build my own ngFor directive.
Conclusion
We have covered a lot of advanced topics in this article. I hope everyone reading this article finds it useful and is able to take something away from it. This is my first article on Medium and I had great fun writing it. If you like it then please follow me on Medium as I plan to keep writing more articles on Angular, TypeScript and JavaScript in general.
More content at plainenglish.io