The elements in the ng-repeat is filtered by toggling bools on the individual elements.
<div class="message-list" infinite-scroll="loadMore()" infinite-scroll-distance="1" infinite-scroll-parent="true">
<div ng-repeat="powerplant in powerPlantFilter = (selectablePowerPlants | filter:searchString | limitTo: numberToDisplay) track by powerplant.ID">
<div class="message" ng-show="powerplant.IsSelected === false && powerplant.IsInSegment === false">
...
</div>
</div>
</div>
My problem is that, if I apply a certain filter, the first element that should be shown, is number 659 in the list.
If I haven't scrolled down so the numberToDisplay is higher than 659, no elements is shown in the list.
The numberToDisplay has a initial value of 35 and is incremented like this.
$scope.loadMore = function () {
if ($scope.numberToDisplay + 20 < $scope.selectablePowerPlants.length) {
$scope.numberToDisplay += 20;
} else {
$scope.numberToDisplay = $scope.selectablePowerPlants.length;
}
};
How do I work around this problem?
JsFiddle demonstrates the problem
I found your problem.
Here is a working jsfiddle : https://jsfiddle.net/3Lsu2ojz/
Your problem what that you were using ng-show instead of a filter..
ng-show can be understood as 'put display: none !important; on this element if condition'
Consequently, it will still be taken in account in the infinite-scroll directive because elements are really here and limitTo actually does its job.
You can use a custom filter, in your case :
$scope.searchString = {'ShowMe': true }
Does the trick.
Also consider using this syntax instead of yours :
powerplant in powerPlantsFilter = ( selectablePowerPlants |
filter:searchString |
limitTo: numberToDisplay) track by powerplant.ID"
Where powerPlantsFilter is here not a collection from the scope but a collection dynamically created by the spec of limitTo and filter.
Related
I have a UI layout with perfect-scrollbar to render a list of items. There are 1600 items which I need to display (without limiting the number of items displayed with any pagination) within the scrollable section so that user can scroll all the items at once (this is a requirement for me and I have less control over this).
The angular template rendering this view is below:
<my-scrollable-section>
<div
ng-class="myCtrl.itemId == item.itemId ? 'item-active-background' : ''"
ng-click="myCtrl.itemClickHandler(item)"
ng-repeat="item in myCtrl.items | filter:myCtrl.search track by item.itemId">
<span>{{item.name}}</span>
<div ng-repeat="(key, value) in ::item.models">
<span>{{::value}}</span>
</div>
<div ng-repeat="(key, value) in ::item.frameworks">
<span>{{::value}}</span>
</div>
</div>
</my-scrollable-section>
The filter in this repeat is linked to a search bar just above this view to narrow down the items being displayed.
The problem now is:
The page does not load instantaneously and freezes for 5-8 seconds. The number of watchers is not the cause for this, as I tried one-time bindings to bring the watcher count below 1500.
Once the page has loaded, the scroll is very slow and does not seem user-friendly at all.
I tried suggesting a pagination to limit the number of items being rendered at a time, but as mentioned earlier, I have little control over the requirements and it's required that all items be present on the scrollable list.
Can these load and performance issues be fixed with angular? Please do not suggest infinite-scroll as even if we use an infinite scroll, in the end, once all items are on the page, the UI will again become slow.
// Try with, on scroll call function & update renderLimit value.
check example here - plunker demo
// set initial limit to say 30.
$scope.renderLimit = 30;
// bind this function with directive.
$scope.updateLimit = function(value){
if(value == 'bottom'){
$scope.contValue += 1;
$scope.renderLimit += 30;
}
};
// directive will be
// custome directive for scrollHandler
app.directive('scrollHandler', function(){
return{
scope: {
scrollHandler: '&',
dataChange:'='
},
link:function(scope,element){
scope.$watch(function(){return scope.dataChange;},function(){
if(element[0].scrollTop > (element[0].scrollHeight - element[0].clientHeight - 50))
element[0].scrollTop=(element[0].scrollHeight - element[0].clientHeight - 50);
});
element.bind('scroll',function(){
var scrPosition = element[0].scrollTop;
if(scrPosition === 0)
scrPosition = "top";
else if(scrPosition === (element[0].scrollHeight - element[0].clientHeight))
scrPosition = "bottom";
scope.$apply(function() {
scope.scrollHandler()(scrPosition);
});
});
},
restrict:"A"
};
});
HTML::
<div scroll-handler="myCtrl.updateLimit" data-change="contValue">
<div
ng-class="myCtrl.itemId == item.itemId ? 'item-active-background' : ''"
ng-click="myCtrl.itemClickHandler(item)"
ng-repeat="item in myCtrl.items| limitTo:renderLimit | filter:myCtrl.search track by item.itemId">
// item contents...
</div>
</div>
Have you looked into vs-repeat?
I've been using this api to handle large number of items to be repeated. And i haven't encountered any problems.
Just a simple:
<div vs-repeat>
<div ng-repeat="item in someArray">
<!-- content -->
</div>
</div>
would solve your problem.
I'm having an issue with ngRepeat :
I want to display a list of students in two different ways. In the first one they are filtered by group, and in the second they are not filtered.
The whole display being quite complex, I use a ngInclude with a template to display each student. I can switch between view by changing bClasseVue, each switch being followed by a $scope.$apply().
<div ng-if="currentCours.classesOfGroup !== undefined"
ng-show="bClassesVue">
<div ng-repeat="group in currentCours.classesOfGroup">
<br>
<h2>Classe : [[group.name]]</h2>
<div class="list-view">
<div class="twelve cell"
ng-repeat="eleve in group.eleves | orderBy:'lastName'"
ng-include="'liste_eleves.html'">
</div>
</div>
</div>
</div>
<div class="list-view" ng-show="!bClassesVue">
<div class="twelve cell"
ng-repeat="eleve in currentCours.eleves.all"
ng-include="'liste_eleves.html'">
</div>
</div>
My problem happens when my list of students change (currentCours here). Instead of refreshing the ngRepeat, both lists concatenate, but only in the unfiltered view.
I tried adding some $scope.$apply in strategic places (and I synchronize my list for example) but it doesn't help.
EDIT : the function used to refresh currentCours in the controller. It's called when a "cours" is selected inside a menu.
$scope.selectCours = function (cours) {
$scope.bClassesVue = false;
$scope.currentCours = cours;
$scope.currentCours.eleves.sync().then(() => {
if ($scope.currentCours.classe.type_groupe === 1) {
let _elevesByGroup = _.groupBy($scope.currentCours.eleves.all, function (oEleve) {
return oEleve.className;
});
$scope.currentCours.classesOfGroup = [];
for(let group in _elevesByGroup) {
$scope.currentCours.classesOfGroup.push({
name: group,
eleves: _elevesByGroup[group]
});
}
$scope.bClassesVue = true;
}
});
utils.safeApply($scope);
};
Well, I found a workaround, but I still don't know why it didn't work, so if someone could write an explanation, I would be very thankful.
My solution was simply to open and close the template each time I switch between views.
I had a Json data I need to create a nested filter search in angular. If you guys can help me since I am new to this I tried but I find difficulty.
I dont have your css but at high level it should be like THIS and for multilevel filter refer THIS
On click of checkbox i am adding filter category to one of my array using addToFilter function.
HTML
<input type="checkbox" ng-checked="item.checked" ng-model="item.checked" ng-click="addToFilter(item.node.category)"/> {{ item.node.category }}
HTML
code filter:categoryFilter will filter records from array according to selected category.
<div ng-repeat="item in nodes | filter:categoryFilter | orderBy:'node.location' | groupBy:['node.location'] ">
<h2 ng-show="item.group_by_CHANGED">{{item.node.location}}</h2>
<ul>
<li>{{item.node.title}}</li>
</ul></div>
JS
This js code is to add selected category from array when checked and remove when unchecked.
$scope.filtersApplied =[];
$scope.addToFilter = function(category)
{
var i = $.inArray(category.trim(), $scope.filtersApplied);
console.log(category);
if (i > -1) {
$scope.filtersApplied.splice(i, 1);
} else {
$scope.filtersApplied.push(category.trim());
}
}
$scope.categoryFilter = function(node) {
if ($scope.filtersApplied.length > 0) {
if ($.inArray(node.node.category.trim(), $scope.filtersApplied) < 0)
return;
}
return node;
}
** Please ignore my grouping code. I just want to simulate what you shown in image.
Updated SAMPLE with CSS
I would like to filter my item store in database, but I don't want to use filter like this :
<li ng-repeat="post in posts | filter: { etat: 'enCours' } ">
This line filter all the posts where etat = 'enCours', but this cause me several problem cause I have several ng-repeat and I can't use $index.
I would like to do something like this :
<li ng-repeat="post in postsEnCours">
With using this function :
This line give me all the post store in my database
$scope.posts= Posts.query();
$scope.postsEnCours = $scope.posts.filter(function(item, index) {
return item.etat === 'enCours';
})
But nothing appears do you know why ?
You shouldn't be depended on ng-repeat's $index .
If you use isolate directive or directive with scope : true in ng-repeat, you won't find $index correctly.
Posts.query() may have callback. If it has callback then you have to put your filter inside it's callback's method.
I have a set of tiles that display a certain number depending on which option is selected by the user. I would now like to implement a sort by whatever number is shown.
The code below shows how I've implemented it (by gettting/setting a value in the parent cards scope). Now, because the orderBy function takes a string, I tried to set a variable in the card scope called curOptionValue and sort by that, but it doesn't seem to work.
So the question becomes, how to I create a custom sort function?
<div ng-controller="aggViewport" >
<div class="btn-group" >
<button ng-click="setOption(opt.name)" ng-repeat="opt in optList" class="btn active">{{opt.name}}</button>
</div>
<div id="container" iso-grid width="500px" height="500px">
<div ng-repeat="card in cards" class="item {{card.class}}" ng-controller="aggCardController">
<table width="100%">
<tr>
<td align="center">
<h4>{{card.name}}</h4>
</td>
</tr>
<tr>
<td align="center"><h2>{{getOption()}}</h2></td>
</tr>
</table>
</div>
</div>
and controller :
module.controller('aggViewport',['$scope','$location',function($scope,$location) {
$scope.cards = [
{name: card1, values: {opt1: 9, opt2: 10}},
{name: card1, values: {opt1: 9, opt2: 10}}
];
$scope.option = "opt1";
$scope.setOption = function(val){
$scope.option = val;
}
}]);
module.controller('aggCardController',['$scope',function($scope){
$scope.getOption = function(){
return $scope.card.values[$scope.option];
}
}]);
Actually the orderBy filter can take as a parameter not only a string but also a function. From the orderBy documentation: https://docs.angularjs.org/api/ng/filter/orderBy):
function: Getter function. The result of this function will be sorted
using the <, =, > operator.
So, you could write your own function. For example, if you would like to compare cards based on a sum of opt1 and opt2 (I'm making this up, the point is that you can have any arbitrary function) you would write in your controller:
$scope.myValueFunction = function(card) {
return card.values.opt1 + card.values.opt2;
};
and then, in your template:
ng-repeat="card in cards | orderBy:myValueFunction"
Here is the working jsFiddle
The other thing worth noting is that orderBy is just one example of AngularJS filters so if you need a very specific ordering behaviour you could write your own filter (although orderBy should be enough for most uses cases).
The accepted solution only works on arrays, but not objects or associative arrays. Unfortunately, since Angular depends on the JavaScript implementation of array enumeration, the order of object properties cannot be consistently controlled. Some browsers may iterate through object properties lexicographically, but this cannot be guaranteed.
e.g. Given the following assignment:
$scope.cards = {
"card2": {
values: {
opt1: 9,
opt2: 12
}
},
"card1": {
values: {
opt1: 9,
opt2: 11
}
}
};
and the directive <ul ng-repeat="(key, card) in cards | orderBy:myValueFunction">, ng-repeat may iterate over "card1" prior to "card2", regardless of sort order.
To workaround this, we can create a custom filter to convert the object to an array, and then apply a custom sort function before returning the collection.
myApp.filter('orderByValue', function () {
// custom value function for sorting
function myValueFunction(card) {
return card.values.opt1 + card.values.opt2;
}
return function (obj) {
var array = [];
Object.keys(obj).forEach(function (key) {
// inject key into each object so we can refer to it from the template
obj[key].name = key;
array.push(obj[key]);
});
// apply a custom sorting function
array.sort(function (a, b) {
return myValueFunction(b) - myValueFunction(a);
});
return array;
};
});
We cannot iterate over (key, value) pairings in conjunction with custom filters (since the keys for arrays are numerical indexes), so the template should be updated to reference the injected key names.
<ul ng-repeat="card in cards | orderByValue">
<li>{{card.name}} {{value(card)}}</li>
</ul>
Here is a working fiddle utilizing a custom filter on an associative array: http://jsfiddle.net/av1mLpqx/1/
Reference: https://github.com/angular/angular.js/issues/1286#issuecomment-22193332
The following link explains filters in Angular extremely well. It shows how it is possible to define custom sort logic within an ng-repeat.
http://toddmotto.com/everything-about-custom-filters-in-angular-js
For sorting object with properties, this is the code I have used:
(Note that this sort is the standard JavaScript sort method and not specific to angular) Column Name is the name of the property on which sorting is to be performed.
self.myArray.sort(function(itemA, itemB) {
if (self.sortOrder === "ASC") {
return itemA[columnName] > itemB[columnName];
} else {
return itemA[columnName] < itemB[columnName];
}
});
To include the direction along with the orderBy function:
ng-repeat="card in cards | orderBy:myOrderbyFunction():defaultSortDirection"
where
defaultSortDirection = 0; // 0 = Ascending, 1 = Descending