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
Related
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>
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!
In need the data from a ng-repeat on two places within the view. The code below works, but I am not sure if this is the way to do it. (I am new to Angular).
If a user clicks on a outlet-option the outlet-products get displayed. First I loaded the products below the outlet-option and used jQuery to move to the productsWrapper if a user clicks on an option. But then I needed to compile it again which made it a bit messy in my opinion.
There can be only one "#productsWrapper" and one ".outlet", so I came up with using the ng-repeat twice. But is this the way to go?
<div id="productsWrapper">
<div ng-repeat="element in elements track by $index" class="products" style="display: none;" id="prod-{{element.wonelement_id}}">
<outlet-product ng-repeat = "option in element.options"
class = "product"
option-data = "option"
element-data= "element"
chosen-data = "chosen">
</outlet-product>
</div>
</div>
<div class="outlet">
<div ng-repeat="element in elements track by $index">
<outlet-option
element-data = "element"
option-data = "element.option"
chosen-data = "chosen"
app-settings = "app">
</outlet-option>
</div>
</div>
I think what you proposed looks fine in a typical AngularJS way.
If you were applying filters to the ng-repeat clause, then I would have suggested applying that filter ahead of time in the controller so that it would only be run the one time. But since you are using no filters at all on the data, I think it is fine as you have it.
I have a ng-repeat with filters put as shown below before. For some reason it didnt work in the web server and i think its because of duplicate items not allowed in browser DOM so i had to put "track by item.id". Please see the code below.
<div ng-repeat="item in items | filter:search:date | filter:filterFrontPage track by item.id">
The only issue with above code is that items are not loaded by having "filterFrontPage" filter as well which filters a boolean value from an item. The whole thing works fine when i have it changed to the following:
<div ng-repeat="item in items | filter:search:date track by item.id">
Thus, using the above, how would i add one more filter to filter a boolean value in a variable. I cant seems to get this working by using "filter:search:date:{isActive:true}". Please let me know as to what can be done to get this working.
Update 1:
I have removed "date" as i used it long ago. "search" is for the following and that works fine.
<input class="form-control" type="text" placeholder="Search posts" ng-model="search.$" />
Ideally filterFrontPage is written to filter items by isActive variable true/false;
I think you need to use parentheses around the filtered collection, i.e.:
<div ng-repeat="item in (items | filter:search:date) track by item.id">
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.