Apologies, the title is horrifically worded.
Background
I have an angular app which I am using as a static display. There is no user interaction so most is based on timeouts. Page is loaded, after X amount of time based on X elements, the page reloads.
Issue
I want to show/hide only portions at at time. For example, one hundred results and I show 10, wait ten seconds, then show the next ten. Again, no user input though.
I am having trouble sorting the elements and hiding/displaying. From my understanding, I believe it is best to use a filter for this similar to pagination with buttons but then how do I trigger that automatically?
I use a JavaScript function to handle fade outs and window reset but I am lost.
I need something like..
<div ng-repeat="item in filtered = items | filter:search | startFrom:(currentPage-1)*entryLimit | limitTo:entryLimit"> -->
Solution
The limitTo and startFrom directives worked perfectly, in my controller I setup a $interval using the angular service and iterated "pages"..
startFrom:currentFlowerPage*flowerPageSize | limitTo: flowerPageSize
If you upgrade to AngularJS v1.4.0 or higher, you can use the limitTo filter to get the behavior you want.
<li class="visible" ng-repeat="x in items | limitTo:entryLimit:entryLimit*currentPage">
you can manipulate entryLimit and currentPage with$timeout as you please to play around with the page number and page size.
AngularJS v1.4.0 limitTo filter doc
Related
I don't really understand how track by works and what it does.
My main goal is to use it with ng-repeat to add some precision.
Using track by to track strings & duplicate values
Normally ng-repeat tracks each item by the item itself. For the given array objs = [ 'one', 'one', 2, 'five', 'string', 'foo'], ng-repeat attempts to track changes by each obj in the ng-repeat="obj in objs". The problem is that we have duplicate values and angular will throw an error. One way to solve that is to have angular track the objects by other means. For strings, track by $index is a good solution as you really haven't other means to track a string.
track by & triggering a digest & input focuses
You allude to the fact you're somewhat new to angular. A digest cycle occurs when angular performs an exhaustive check of each watched property in order to reflect any change to the correspodant view; often during a digest cycle it happens that your code modify other watched properties so the procedure needs to be performed again until angular detects no more changes.
For example: You click a button to update a model via ng-click, then you do somethings (i mean, the things you wrote in the callback to perform when an user makes a click), then angular trigger digest cycle in order to refresh the view. I'm not too articulate in explaining that so you should investigate further if that didn't clarify things.
So back to track by. Let's use an example:
call a service to return an array of objects
update an object within the array and save object
after save service, depending on what the API returns, you may:
replace the whole object OR
update a value on the existing object
reflect change in ng-repeat UI
How you track this object will determine how the UI reflects the change.
One of the most annoying UXs I've experienced is this. Say you have a table of objects, each cell has an input where you want to in-line edit those objects' properties. I want to change the value, then on-blur, save that object while moving to the next cell to edit while you might be waiting on the response. So this is an autosave type thing. Depending on how you setup your track by statement, you may lose current focus (e.g. the field you're currently editing) when the response gets written back into your array of objects.
When you add track by you basically tell angular to generate a single DOM element per data object in the given collection.
You can track by $index if your data source has duplicate identifiers.
If you do need to repeat duplicate items, you can substitute the default tracking behavior with your own using the track by expression.
Example:
[{id:1,name:'one'}, {id:1,name:'one too'}, {id:2,name:'two'}]
Try to use the duplicate values in ng-repeat, you will get an error such as:
Error: ngRepeat:dupes Duplicate Key in Repeater
To avoid this kind of problems you should use track by $index. For example:
<ul>
<li ng-repeat="item in [1, 2, 3, 3] track by $index">
{{ item }}
</li>
</ul>
Here is how you would get $index in nested ng-repeat:
<div ng-repeat="row in matrix">
<div ng-repeat="column in row">
<span>outer: {{$parent.$index}} inner: {{$index}}</span>
</div>
</div>
Here are some resources that may help you:
track by $index documentation
ngRepeat documentation
2014 codelord.net article about ng-repeat performance and track by
You should use track by only if you need to go against the default behaviour of ng-repeat which is to remove duplicate items.
You can track the items using the scope property $index or specifying a custom function.
For instance:
<div ng-repeat="x in [42, 42, 43, 43] track by $index">
{{x}}
</div>
Display all values of the array (42 is displayed twice).
For reference: https://docs.angularjs.org/api/ng/directive/ngRepeat
Let's say, we have the following list:
<ul>
<li ng-repeat="item in items">
{{ item }}
</li>
</ul>
where, an item has the following structure:
{ 'id'=>id, 'name'=>name, 'description'=>description }
There is no problem whatsoever in this list until we wish to update it. For our own convenience we replace the list of items, with another updated list of items, as such:
items = newItems;
However, in this new list, few items change. Most items remain the same. Unfortunately Angular does not know how to identify our items and map them to the respective <li> elements, so it just deletes all elements and creates them again. This is extremely performance-costly in some cases and here is where track by comes in use.
By adding the track by clause to the element
<li ng-repeat="item in items track by item.id">
we are informing Angular, that the unique identifier of our items is item.id. Now Angular knows not to recreate all items, but only items with new ids, and only updates the rest. The performance improvement is significant in most cases. Also, personally, I like that I can monitor my items easier on my browser's developer tools, because they don't disappear every time I update them.
I am implementing the table view .In table view I am implementing infinite scroll .In other word I have 2000 objects .I am showing 25 element at one time .When user scroll to bottom it load more 25 elements so on .. I have "V" or "^" button on header .On click of this image I want to display in descending order only last 25 elements .when scroll more it load more data so on ..So I added filter in that but it is not working ..could you please tell me how we can implement this ..
I will give you example :Like A, B,D,C,K,N....T,Z objects when application load object is load like that .when user click it show Z,y,X ,W,V,U...A or again ascending way "A,B,C...Z"
here is plunker
$scope.setSort = function(idx, reverse){
$scope.sortval = 'columns['+idx+'].value';
$scope.reverse = reverse;
};
Html
<div class="col col-center brd gray-20" ng-repeat="d in invoice_column_name | filter:{checked: true}">
<sort-header label="{{d.label}}" index="{{d.index}}" sort-exp="setSort(idx, reverse)"></sort-header>
EDIT
Update
I will explain again I have 2000 objects ..It is display randomly .I am display only 25 object at one time .When user scroll more data it show again 25 data..But I want to sort that data in ascending as well as descending way .First I need to sort the 2000 objects then display 25 elements from that and than if user want to load more it scroll data and load that.In your example it is sorting first 25 object only .then load another 25 elements then sort 5o elements ..so on ..so it is not correct solution –
Update plunker
If you are dealing with a large collection of data, then it might be better to load the whole collection first. I have a couple of reasons for this:
Sorting the results on each load operation can be taxing for older browsers, especially as more data is brought in. This will slow down your UI.
If the data is not provided in order by the source, then the process of sorting might scatter the last-loaded set of items over several "pages" of the view before the one that your user is looking at. They will then have to search backwards through the results for the new information.
Try with with this library https://github.com/sroze/ngInfiniteScroll
First you need to load data from server or whatever you like, than in your controller you have to implement one function that gets called when user scrolls down. Also you can order your data in your controller
$filter('orderBy')(data, orderProp, desc/asc)
see angularjs filter order by. When user clicks on letter you have to order your data and search for first element in the array that starts with that letter. All you have to do next is to implement this in the ngInfinitScroll directive callback function.
Let's suppose a list of 1000 items displayed with infinite scrolling.
Each item displays: a person's firstName, lastName, and mood. (to make it simple)
Initially, I didn't want to listen for updates.
So the great angular-bindonce directive or even better: angular 1.3 one-binding feature made the trick.
Now, I created a pull-to-refresh component, allowing to refresh the whole items.
However, as binding once, (and not reloading the page) my whole list didn't take the updates in account.
Using angular-bindonce, I have this currently:
<div bindonce ng-repeat="person in persons track by person.id">
<span bo-text="person.firstName"></span>
<span bo-text="person.lastName"></span>
<span bo-text="person.currentMood"></span>
</div>
The pull-to-refresh triggers this function:
$scope.refresh() {
Persons.getList(function(response)) {
$scope.persons = response.data; //data being an array
}
}
Question is:
Is there a way to refresh all the data ONLY when the pull-to-refresh is triggered?
In this case, I would be able to keep this one-binding that would greatly improve performance when dealing with huge lists of persons.
Until now, I'm forced to....use two-way binding, the natural way of Angular works.
More generally, how to deal with huge lists with infinite scrolling that needs to be updated only when some events are triggered?
Get angular-bind-notifier.
Use native bindings (with a somewhat modified syntax) and setup your markup like so:
<div ng-repeat="person in persons track by person.id" bind-notifier="{ eventKey:watchedExpression }">
<span>{{:eventKey:person.firstName}}</span>
<span>{{:eventKey:person.lastName}}</span>
<!-- or with ng-bind if you prefer that -->
<span ng-bind=":eventKey:person.currentMood"></span>
</div>
Now, whenever the value of watchedExpression changes - a $broadcast will be sent down through the childscope created by bind-notifier and tell every binding with the :key:expr syntax to re-evaluate.
If you need to, you can also send the $broadcast manually in the following format:
$scope.$broadcast('$$rebind::' + key) // where 'key' === 'eventKey' in the example above.
refresh-on directive could do the trick, found a reference HERE:
<div bindonce="persons" refresh-on="'refresh'" ng-repeat="person in persons track by person.id">
<span bo-text="person.firstName"></span>
<span bo-text="person.lastName"></span>
<span bo-text="person.currentMood"></span>
</div>
Instead of trying to work around not using two-way binding but still have all of its benefits there is more likely and easier solution. You say that there are 1,000 rows, are all 1,000 rows with the viewport / visible to the user at once?
I would assume not, so I would suggest using a buffered view for the list of items. Buffering the rows would mean that the rows that are not visible have no bindings but still take up space in the DOM so the scroll bar is always accurate.
The one major caveat of buffering is that all rows should be the same height, no variable height rows.
Here are some virtual scrolling / buffering directives to take a look at:
https://github.com/EnzeyNet/VirtualScroll
https://github.com/stackfull/angular-virtual-scroll
https://github.com/kamilkp/angular-vs-repeat
I am using Bootstrap UI in my angular application. I have a tooltip in the html page which works fine. I noticed that after the tooltip is displayed and I move my mouse out, the Ui-bootstrap-tpls.js fires a method called "hideTooltipBind" which in turn calls $apply and it triggers the filters in that scope to reload.
Lets say I have 10 filters in the scope which is filtering an array of 100 each. Everytime a tooltip is displayed, all my filters are forced to reload again. How can I avoid this?
I am using
//ajax.googleapis.com/ajax/libs/jqueryui/1/jquery-ui.min.js
jquery-2.0.3.js
ui-bootstrap-tpls-0.11.0.js
I have attached the screenshot of the Call Stack
You can utilise some form of one-time binding. There are multiple options out there:
bind-once by pasvaz
angular-once by tadeuszwojcik
watch-fighters by abourget
fast-bind by me (fork off of Karl Seamons work)
There are some differences to the four (unrelated to your question at hand however):
bind-once is the most popular one seeing the most active development. Requires two directives to do the job (bindonce and bo-*).
angular-once is the minimalist of the four (don't quote me on that).
watch-fighters doesn't handle promise based data.
fast-bind has a notifier system for semi-static bindings, using the event bus in Angular.
Assuming you'd start leveraging either one of them, your bindings could look something like this:
<div bindonce="someData">
<span bo-bind="someData.text | yourFilter"></span>
</div>
<span once-text="someData.text | yourFilter"></span>
<span set-text="someData.text | yourFilter"></span>
<span bind-once="someData.text | yourFilter"></span>
This way, your filters would not reevaluate on Angular calls to $digest. If you are filtering a collection in your view (<li ng-repeat="coll | filter"></div>), I'd suggest you move those filters to the controller to reduce the amount of calls to the filters themselves.
Say, I've got multiple tabs and I want to use angular-straps to display that. However, the content of each tab is dynamic based on an ajax call for that given tab. Ideally could be derived from the tab id.
| Tab 1 | Tab 2| Tab 3|
So when Tab 1 is clicked, a call to the server is /get/result/tab1_id/ and when Tab 2 is clicked, a call to the server is /get/result/tab2_id etc... And use this result to populate a ng-grid somehow.
Angular has a lot of potential, but I am so new to it and a lot of ways of doing things and I am not good in any. This stuff can be done in jquery in no time with jtable etc.. but I am trying to learn angular js.
So please help.
Thanks & regards
Tin
To determine which tab is clicked, you can do
<div data-fade="1" ngModel="activeTab" bs-tabs>
<div ng-repeat="tab in tabs" data-title="{{tab.title}}" ng-click="loadData()"><p>{{tab.content}}</p></div>
</div>
In the controller, you can do:
$scope.loadData = function(){
switch(activeTab){
case 0:
//load data
break;
case 1:
....
}
}
To load data, you can look at $http or $resource module.