Performance Tuning Angularjs ng-repeat for large lists - angularjs

I have an angularjs directive I have written that acts like a form select component. It has a search field to search through the choices. When you click on a choice it removes the choice from the list of choices and displays it, above the search field, as a selection. When you click on a selection it removes it from the selections, above the search field, and places it back in the choices list.
I have done a lot of research on ng-repeat and it's potential performance issues, which I have seen. In my current iteration I am using bindonce, limitTo and track by, and an infinite scroll. On my laptop this directive is very fast, on iPhone 5, still very fast, on iPhone 4 pretty fast and on iPhone 3 junk. On iPhone 3 the text search filtering through the choices is fast, but the selecting a choice or selection is where it is slow.
Here is the ng-repeat I am using for choices:
<a bindonce refresh-on="'refreshSelect'" ng-repeat="choice in choices | filter:{name: searchFilter.name, selected: false}:strict | orderBy:'name' | limitTo: limitNumberOfChoices track by choice.id" ng-click="addToSelections(choice)">
<div class="well well-sm" style="margin-bottom:10px;">
<strong>
<span bo-bind="choice.name"></span>
<span class="badge" ng-show="choice.badgeValue" bo-bind="choice.badgeValue"></span>
</strong>
</div>
</a>
Here is the ng-repeat I am using for selections:
<a bindonce refresh-on="'refreshSelect'" ng-repeat="selection in selections | orderBy:'name' track by selection.id" ng-click="removeFromSelections(selection)">
<div class="well well-sm" style="margin-bottom: 10px;">
<strong>
<span bo-bind="selection.name"></span>
<span class="badge" ng-show="selection.badgeValue" bo-bind="selection.badgeValue"></span>
</strong>
</div>
</a>
A plunker of my directive is at http://plnkr.co/edit/ziQstcBHXsMyIPNnetur?p=preview
Choices are below the input field. Selections are above the input field. You can scroll through the choices. infiniteScroll will fire when you have scrolled 75% down the list of choices. This will add 50 to the limitTo in ng-repeat. The choices array is 10000 items large.
I need suggestions on how to speed this directive up. Unfortunately we have a large number of iPhone 3s, getting rid of them is not an option. I am probably doing something real simple that is slowing it down.
Thanks in advance
Warren
UPDATED
I took off the orderBy on the ng-repeat for the choices list. Choices is the large list with 10000 records. This increased the speed a bit but still slow on iPhone 3.

Related

Angularjs Select option Performance Improvement - Gropup By Filter for 3000+ datas

I am having select dropdown which filters data corresponding to selected option. If nothing is selected then it shows 3000+ records which is freezing my UI for few seconds. But for other option switching which has few hundreds data is perfect and fast. Is there any way to improve performance here?
Following is the dropdown filter and citylist is an array which gets datas from server.
Select City:<span class="float-right fa fa-window-close mb-2"
ng-click="sm.selectedCity=undefined;">
</span>
<select ng-model="sm.selectedCity" >
<option value="" selected hidden />
<option ng-repeat="s in sm.citylist"
ng-selected="sm.selectedCity" ng-value="">
{{s.value}}
</option>
</select>
Following UI data varies based on selected city option above. Initially none of the option is selected which fetches more than 3000 datas for one of the grouped Country object.
To briefly explain below UI...sampledata is a complex object filled from server. sampledata contains Country property which is grouped and it can be collapsed or expanded by clicking plus/minus at its right side. Based on selected city filter Grouped Country will change. Its working fine for less data. But if one of the Country has more datas like (3000+) my UI freezes while clearing the filter
<div ng-repeat="(key, value) in sm.sampledata | filter:{STFilter:sm.selectedCity} | filter:{someother } | filter:search.number | orderBy:'Country' | groupBy: 'Country' track by $index">
<div class="mb-5">
{{ key }} ({{ value.length }})<span ng-class="sm.toggle[$index] === true ? 'fa fa-minus' : 'fa fa-plus'" ng-click="sm.toggle[$index] = !sm.toggle[$index]" class="pull-right"></span>
</div>
<div ng-show="sm.toggle[$index]">
<div ng-repeat="o in value">
//o is a complex object in which one of the object needs to be filtered based on selected option from dropdown
<div>
{{o.sample1}}<span class="pull-right">{{o.sample2}}</span>
</div>
//Still more will come
</div>
</div>
<hr />
</div>
Note: I am using angular 1.7.2 version
To increase performance, use the ng-options directive.
From the Docs:
Choosing between ngRepeat and ngOptions
In many cases, ngRepeat can be used on <option> elements instead of ngOptions to achieve a similar result. However, ngOptions provides some benefits:
more flexibility in how the <select>'s model is assigned via the select as part of the comprehension expression
reduced memory consumption by not creating a new scope for each repeated instance
increased render speed by creating the options in a documentFragment instead of individually
Specifically, select with repeated options slows down significantly starting at 2000 options in Chrome and Internet Explorer / Edge.
For more information, see
AngularJS <select> Directive API Reference - Choosing between ngRepeat and ngOptions
AngularJS ng-options Directive API Reference
Select City:<span class="float-right fa fa-window-close mb-2"
ng-click="sm.selectedCity=null">
</span>
<select ng-model="sm.selectedCity"
ng-options="s.value as s.value for s in sm.selectedCity">
</select>

When does duplicate digest computations warrant redesign in AngularJS?

I have been working with AngularJS for a while now and this is one subject I still am not sure about. Let me start with an example. In my site I have a table with data. I want to be able to search/sort/filter this data in a number of ways. We have a search box that utilizes the built in "filter" filter to search the data. We also have a number of other filters that allows the user to check checkboxes and get different views (think Amazon).
The issue is because things like pagination need to know about these filters and must apply its computations based off the filtered view of data, not the original data set.
Doing so makes our HTML look like this:
<div class="row paginationInfo clearfix">
<div class="col-xs-3 showResultsWrap">
<span data-name="summary"><b>{{ getPageStart2((model.IndividualMailboxes.filtered | pipe:model.IndividualMailboxes.filters.functions:model.IndividualMailboxes.filters.values), model.IndividualMailboxes.currentPage, model.IndividualMailboxes.resultsPerPage) }} - {{ getPageEnd2((model.IndividualMailboxes.filtered | pipe:model.IndividualMailboxes.filters.functions:model.IndividualMailboxes.filters.values), model.IndividualMailboxes.currentPage, model.IndividualMailboxes.resultsPerPage) }}</b> of <b>{{ (model.IndividualMailboxes.filtered | pipe:model.IndividualMailboxes.filters.functions:model.IndividualMailboxes.filters.values).length }}</b></span>
</div>
<div class="col-xs-6 text-xs-center">
<div max-size="5" boundary-links="true" first-text="«" last-text="»" previous-text="‹" next-text="›" uib-pagination ng-show="(model.IndividualMailboxes.filtered | pipe:model.IndividualMailboxes.filters.functions:model.IndividualMailboxes.filters.values).length > 10" total-items="(model.IndividualMailboxes.filtered | pipe:model.IndividualMailboxes.filters.functions:model.IndividualMailboxes.filters.values).length" ng-model="model.IndividualMailboxes.currentPage" items-per-page="model.IndividualMailboxes.resultsPerPage" class="pagination-md"></div>
</div>
<div class="col-xs-3 resultsPerPageWrap form-inline text-xs-right">
<span>View</span>
<select sk-id-gen="resultsPerPage" name="ResultsPerPage" class="form-control m-l-1" ng-model="model.IndividualMailboxes.resultsPerPage" ng-options="val as val for val in [10, 20, 50, 100]">
</select>
</div>
</div>
The important thing to notice here is how many times we have to do (hereIsMyArray | pipe:param1:param2).length over and over again for the pagination to work.
So correct me if I am wrong, but I believe every digest cycle we are running these pipes/filters 5+ times. This is probably OK with a small dataSet, but if I start having to filter 10,000 items then I'm sure I will see performance issues.
It would be much more efficient if we only had to do the pipe/filtering 1 time and each of these HTML components could just use that single instance of the filtered data set.
The issue with not doing it directly in the HTML is you no longer have automatic watches setup to see when data changes and updates the view.
If any of my statements are wrong please let me know. I am curious if anyone has had this issue before and has a solution?
Thanks

Angular vs-repeat performance issue with tables with 7k rows

I am using angular vs-repeat to render around 7k rows in a table. If I use vs-repeat for table body, the rendering was very slow.
<table class="table">
<tbody vs-repeat style="width: 100%;">
<tr ng-repeat="row in list track by $index">
<td>{{::row[listColumns[0].colName]}}</td>
<td>{{::row[listColumns[1].colName]}}</td>
<td>{{::row[listColumns[2].colName]}}</td>
<td>{{::row[listColumns[3].colName]}}</td>
<td>{{::row[listColumns[4].colName]}}</td>
</tr>
</tbody>
If I use vs-repeat without tables, in my case I used divs. Its rendering very fast
<div vs-repeat class="table-body">
<div class="row" ng-repeat="row in list track by $index">
<div class="col-md-4">{{::row[listColumns[0].colName]}}</div>
<div class="col-md-2">{{::row[listColumns[1].colName]}}</div>
<div class="col-md-2">{{::row[listColumns[2].colName]}}</div>
<div class="col-md-2">{{::row[listColumns[3].colName]}}</div>
<div class="col-md-2">{{::row[listColumns[4].colName]}}</div>
</div>
</div>
Please suggest how to improve performance for table?
With vast collections of items that render many DOM elements, even the most negligible processing of items inside an ng-repeat can choke your app.
DOM-related solutions that check if elements are in view don't tell you if nested components have been loaded and rendered completely, leading to errors.
My solution to this problem is angular's limitTo : limit: begin filter, along with iScroll or native scrollTop and scrollBottom callbacks, which raise or reduce the begin index in the controller according to the browse direction - sort of frontend pagination where only a specific number of items are shown at the DOM.
<tr ng-repeat="item in items | limitTo : 100 : 600 track by $index">
<span>{{ item.property }}</span>
</tr>
Then you can find the optimal limitTo number that work out best for the app - while keeping it light, quick, and agile as Angular should be.
use ng-repeat on small set of an array. Do pagination or lazy loading. store data to temporary array which you want to display rather having n-repeat on 7k records.
Thoughts?
Thanks!

Angular performance in ng-repeat

I was faced the performance issue of ng-repeat directive, I rendered the PDF formFields using ng-repeat directive, somehow it halts my browser. But when I use track by $index and limitTo:1 together in same ng-repeat, it works fine and it enhances the rendering speed. I can't understand the logic behind this but it works extraordinary fast :)
This works faster and fine for me, also limitTo:1 not work, some how it is binding the limit with $index and halting of browser issue fixed.
<div ng-repeat="friend in friends track by $index | limitTo:1">
{{ friend.id }} — {{ friend.name }}
</div>
if we use limitTo before track by $index than it behaves normally the limit behavior of angular js which is understandable
<div ng-repeat="friend in friends | limitTo:1 track by $index">
{{ friend.id }} — {{ friend.name }}
</div>
OR
<div ng-repeat="friend in friends | limitTo:1">
{{ friend.id }} — {{ friend.name }}
</div>
Although i achieve my performance goal, but i want to know the logic behind this.
Here is the link of jsfiddle
http://jsfiddle.net/neglingeyes/G6q84/
You can not measure multiple ng-repeat directives at once with post-repeat.
This is because the way it tries to figure out when the rendering is complete is to set a timeout function for the next event loop tick, and that really means all the directives on the page have finished rendering, not just this specific ng-repeat.
In your demo fiddle, I guess each next post-repeat timeout gets processed in a separate event loop tick or something like that, and this is why their "measured" performance seems to get worse and worse. If you changed their order, again the first will seem to be the fastest. But only by a couple of ms - a trivial difference.
Have you tried to benchmark each case separately? Also, on what platform and with what data? On desktop browsers and with such short data it is too fast to make a difference either way.
As to what the first code segment does - it is parsed as friend in friends track by ($index | limitTo:1) ("What brackets?" ;-) )
The limitTo filter can only be applied to arrays, not to scalar variables like the number $index. But the angular filters normally return their input unchanged if they can not make sense of it, so I guess that is what happens here too.
Here is an explanation:
by default ng-repeat creates a dom node for each item and destroys that dom node when the item is removed; As it watches the ng-repeat object, it expects the object to change & implement the change & hence keep adding or deleting nodes. But using $index it reuses DOM nodes.
Here is the link where I picked up this information- http://www.reddit.com/r/angularjs/comments/2cvo16/speeding_up_angularjs_with_simple_optimizations/

correct strategy to generate html dynamically with angular

I have a huge JSON object tree with two levels. First level has around 500 elements, and each element contains an average of 100 child elements.
I want to display the first level of the tree and I am doing it with a simple ng-repeat. When the user clicks on the element I want to display the child elements of that element. If I use a span ng-switch or a ng-show to show/hide child elements when the page first renders it freezes for around 10 seconds while generating all the HTML.
It doesn't sound like the right solution. There must be a different way of doing it, but I can't figure out. Anyone knows?
I have explained most in my comment, and here is a working plunker:
http://plunker.co/edit/RSZwfLlsCJ68MUkACbdp?p=preview
the new ng-if directive will do what you want
<h1>ng-if</h1> <h5>Click on the level to expand</h5>
<div class="well">
<ul class="nav nav-list" ng-repeat="(attr,element) in tree">
<li ng-click="expand=!expand" ng-class="{'active':expand}"><a>{{element.name}}</a></li>
<ul ng-if="expand" class="nav nav-list">
<li ng-repeat="item in element.items">{{item.name}}</li>
</ul>
</ul>
</div>
you can also do this in the "old-way" with ng-show using new ternary operator or its alternative expr && if_true || if_false
<h1>old-way</h1> <h5>Click on the level to expand</h5>
<small>use ternary operator or <pre>expand && element.items || []</pre></small>
<div class="well">
<ul class="nav nav-list" ng-repeat="(attr,element) in tree">
<li ng-click="expand=!expand" ng-class="{'active':expand}"><a>{{element.name}}</a></li>
<ul ng-show="expand" class="nav nav-list">
<li ng-repeat="item in (expand ? element.items : [])">{{item.name}}</li>
<!--<li ng-repeat="item in (expand && element.items || [])">{{item.name}}</li>-->
</ul>
</ul>
</div>
See this answer on ng-repeat performance. Essentially, it just takes a long time since Angular's ng-repeat, and basically all other directives, are set up to always look for updates in the whole JSON structure. So if you have lots of data and don't need live updates in the HTML view when changing the JSON, I wouldn't recommend using AngularJS. Generally, AngularJS performance also depends a lot on the browser and its JavaScript engine.
Alternatively, you could divide your JSON into subparts and then use pagination to display it.
I would recommend to fetch the data gradually from the server. Use server-side pagination and retrieve only the fields that you are going to display. Then, when a user clicks on one of the first level items, you can do another XHR to the server with the new data. I had similar requirements for a project and that solved the latency issue.
Regards,
Agustin.

Resources