One of the requirements for the app we develop was implementation of accordion list witch would represent table of content of a book. The first implementation was straight forward and was pretty much the copy of this CodePen example. The problem of that implementation is pure performance. Some of the books contain more than 80 chapters and each chapter contain a lot of paragraphs and some of the paragraphs could also contain sub-paragraphs and so on...
Lets analyze the code from CodePen:
1. it contains ng-repeat which obviously slows down our app
2. each item in this list contain ng-click, ng-class, ng-show and another ng-repeat.
This architecture lead us to very pure performance specially on relatively old devices (I have Samsung galaxy s2 for instance). And it is pretty clear that angular digest loop will suffer because of approximately 2000 watchers just for your table of content.
So the firs step was to get rid of all ng-repeat directives.
The idea is to pre-render table of content of a book as a plain HTML text and save it to device's file system.
After that plaine HTML text should be wired-up with a view using
Lets analyze the code from CodePen:
1. it contains ng-repeat which obviously slows down our app
2. each item in this list contain ng-click, ng-class, ng-show and another ng-repeat.
This architecture lead us to very pure performance specially on relatively old devices (I have Samsung galaxy s2 for instance). And it is pretty clear that angular digest loop will suffer because of approximately 2000 watchers just for your table of content.
So the firs step was to get rid of all ng-repeat directives.
The idea is to pre-render table of content of a book as a plain HTML text and save it to device's file system.
After that plaine HTML text should be wired-up with a view using
$("#indexview").append($compile(plainHtml)($scope));
With a help of handlebars.js it's quite easy to pre-render plain HTML text. here are the recursive templates that I use for rendering table of content down to bottom paragraph level:
var chapterTocTemplate = '\
<ion-list>\
{{#each Chapters}}\
<ion-item id="i-{{Id}}" class="ii" chapter="{{Id}}" anchor="{{Id}}">\
<n>{{TitleNumber}}</n>\
<tc>{{TitleText}}</tc>\
<tb class="tb ion-chevron-down"></tb>\
</ion-item>\
<cp">\
{{#if Paragraphs}}{{> tocParagraphs }}{{/if}}\
</cp>\
{{/each}}\
</ion-list>';
var paragraphTocTemplate = '\
{{#each Paragraphs}}\
<pi chapter="{{ChapterId}}" anchor="{{Id}}">\
<n>{{TitleNumber}}</n>\
<tp>{{TitleText}}</tp>\
</pi>\
{{#if Paragraphs}}\
<sb>{{> tocParagraphs}}</sb>\
{{/if}}\
{{/each}}';
It is important here that you don’t use list of classes
<div class='classA classB classC classD'>{{TitleNumber}}</div>
because this will dramatically increase the size of the plain HTML text. Instead of that you should define styling for a new element:
<n>{{TitleNumber}}</n>
It’s good to know that scss style of a new element can extend existing styles:
n {
@extend .classA;
@extend .classB;
...
@extend .classD;
}
As you have also noticed I don’t use any ng-click or ng-class directives in templates in order to reduce amount of watchers in the digest loop.
The last step is compiling our plain text into the $scope of a view to wire-up ionic tags that we did use in our template with ionic scope.
At this step we have solved our performance problem, but we still need to wire-up at least click actions:
The easiest way is to register click listener to the whole view. The object that is passed through the callback event contains information about element that you have clicked on. I have created custom tags (see handlebars template) for all clickable elements and just compare in the callback
event.srcElement.tagName
with the tag I need. My clickable paragraphs also contain an additional attributes chapter and anchor. It is easy to have access to these attributes in my click event callback:
event.srcElement.attributes.chapter.value;
I got rid of ng-show and ng-class directives. Instead of those I have to use java script manipulations which is not the best practice in general but doing this I have got an additional profit in performance:
$('#someid').find('cp')[0].style.display = 'none';
event.srcElement.className = event.srcElement.className.replace(' ion-chevron-up', '') + ' ion-chevron-down';
