Angular loop through JSON with a limited number - angularjs

Angular has a built-in feature to loop through JSON, for example I can do:
$scope.users = data.users
But I want to only loop through a number of users to enhance the performance like:
$scope.users = data.users[5] to data.users[10]
How would I be able to do this?
P.S. for pure javascript I can do:
for(var i = 5; i <= 11; i++) {
var user = data.users[i];
}
Of course this cannot add user to the scope.

One way is to use limitTo filter in view
<div ng-repeat="item in items | limitTo : limit.itemCount : limit.startIndex">
And in controller set variables that you can easily change to show different items or quantities
$scope.limit={
startIndex :0,
itemCount : 10
}
$scope.next = function(){
$scope.limit.startIndex += $scope.limit.itemCount;
}

You can use the slice method of an array.
$scope.users = data.users.slice(5, 11);

Related

reorder list randomly in ng-repeat

I have an ng-repeat as following :
<ul>
<li ng-repeat="o in villes | limitTo:5">{{o.nomVille}}</li>
</ul>
I want to reorder the villes list randomly before I limit it to 5, so every time I open my page I get 5 different villes each time.
is there a filter in angularjs who can do that for me ?
edit :
I created a costum filter to randomize that list as following :
.filter('random', function() {
return function(val) {
let shuffle = (a) => {
let r = [];
while (arr.length)
r.push(
arr.splice( (Math.floor(Math.random() * arr.length)) , 1)[0]
);
return shuffle(val);
}
};
});
and in ng-repeat I did this :
<li ng-repeat="o in villes | random | limitTo:5">{{o.nomVille}}</li>
but I cant no longer see anything in my page.
this is the example on jsfiddle : https://jsfiddle.net/z10wwhcv/
You'd have to build a custom filter function that randomizes the order and then apply the filters in the correct order (random before limit).
Have a look at this for details:
Using AngularJS how could I randomize the order of a collection?
If you want the order to change each time you load the page you can't do it as a filter as that will presumably change on each digest cycle. You need to store villes on the scope somewhere and generate it in a random order when the page loads, e.g. in your controller function for that page.
use the filter in the controller (which is also a best practice performance boost):
$scope.shuffled = $filter('random',$scope.villes)
you'll need to inject the service in the controller
this is what your filter should look like ( not tested but should work ):
.filter('random', function() {
return function(a) {
var r = [];
while (a.length)
r.push(
a.splice((Math.floor(Math.random() * a.length)), 1)[0]
);
return r;
}
}
This is the solution I created :
shufflemodule.filter('shuffle', function() {
var shuffledArr = [],
shuffledLength = 0;
return function(arr) {
var o = arr.slice(0, arr.length);
if (shuffledLength == arr.length) return shuffledArr;
for(var j, x, i = o.length; i; j = parseInt(Math.random() * i), x = o[--i], o[i] = o[j], o[j] = x);
shuffledArr = o;
shuffledLength = o.length;
return o;
};
});
http://jsfiddle.net/aimad_majdou/6mngvo38/

How to apply two inclusive filters in Angular?

<tr ng-repeat="thing in things| filter: {toDpt: filterReceived} && {fromDpt: filterSent}">
I'm trying to show things from AND to my department(dpt). My filterReceived and filterSent are being toggled with two buttons. Both are working.
The problem is: only the first filter is working. If I put fromDpt before toDpt, only fromDpt will work.
Thanks for any advice.
The goal os this code was to display tickets from department or to deparment. The $scope.filters contains the filters to be applied. A user can see, simultaneously or not, tickets that his department has sent or received.
Using Angular Filters, I was able to call my ticketsFilter function, passing in the tickets as the first default argument and filters as second optional argument.
Controller:
$scope.filters.toDpt = 'Finance';
$scope.filters.fromDpt = 'Food';
Filter:
app.filter('ticketsFilter', function (){
return function (items, filters){
console.log
var filtered = [];
for (var i=0; i < items.length; i++){
var item = items[i];
//CHECKING FROM AND TO DPT.
if (item.fromDpt == filters.sent){
filtered.push(item);
} else if (item.toDpt == filters.received){
filtered.push(item);
};
};
return filtered;
};
});
Markup:
<tr ng-repeat="ticket in tickets | ticketsFilter:filters">

ng-options filter (range selecting)

I can't really explain properly what I want but I try to make a ng-options in angularJS work:
<select ng-options="object.id as object.name for object in objects" ng-model="selected"></select>
So the current output would be :
1950, 1951, 1952, 1953, 1954, 1955, 1956, 1957, 1958, 1959, 1960 ...
What I want to achieve is:
1950 - 1954, 1955 - 1959, ....
So it will be displayed by every X year.
Is there any way to achieve this?
I tryed it with limitTo and then every 5th time + 5 but no success.
Has anyone a idea?
I'd personally just push that range-grouping logic into the javascript controller. Then you can bind to the array of pre-grouped ojects.
Javascript:
$scope.groupedObjects = [];
var groupDictionary = {}; //for keeping track of the existing buckets
var bucketId = 0;
//assuming objects is an array I prefer forEach, but a regular for loop would work
$scope.objects.forEach(function (obj) {
var yearBucket = Math.floor(obj.name / 5) * 5; //puts things into 5-year buckets
var upperBound = yearBucket + 4;
var bucketName = yearBucket + '-' + upperBound; //the name of the bucket
if (!groupDictionary[bucketName]) { //check whether the bucket already exists
groupDictionary[bucketName] = true;
$scope.groupedObjects.push( {id: "id" + bucketId, name: bucketName} );
bucketId += 1;
}
});
And just use groupedObjects
<select ng-options="object.id as object.name for object in groupedObjects"
ng-model="group"></select>
Here's a plunker demonstrating this idea.

Filter a ng-repeat with values from an array

I have this table with a ng-repeat.
ng-repeat="project in projects"
I have a property in project, prj_city. I'd like to filter this value.
I can do this with:
ng-repeat="project in projects | filter={prj_city: <value>}
But I want the <value> to be an array with multiple cities instead of a string. Is there any easy way to do this or do I have to do this filter manually in my controller?
Most likely a custom filter in the controller, should be easy enough tho:
var filteredCities = ["LosAngelos", "etc.."];
$scope.arrayFilter = function(project) {
for (var i = 0; i < filteredCities.length; i++) {
if (filteredCities[i] == project.prj_city)
return true;
}
return false
}
And the call:
ng-repeat="project in projects | filter: arrayFilter"
You need to create a filter function on your controller. Something like:
$scope.filteredCities = function(city) {
return ($scope.userFilteredCities.indexOf(city) !== -1);
};
$scope.userFilteredCities;//List of filtered cities
Define the following function in your controller:
// use a map for faster filtering
var acceptedCityMap = {};
angular.forEach(acceptedCities, function(city) {
// case insensitive search. But you're not forced to
acceptedCityMap[city.toLowerCase()] = true;
});
$scope.isProjectedAccepted = function(project) {
// case insensitive search. But you're not forced to
return acceptedCityMap[project.prj_city.toLowerCase()];
}
And then in your view:
ng-repeat="project in projects | filter:isProjectAccepted"

angular grouping filter

Following angular.js conditional markup in ng-repeat, I tried to author a custom filter that does grouping. I hit problems regarding object identity and the model being watched for changes, but thought I finally nailed it, as no errors popped in the console anymore.
Turns out I was wrong, because now when I try to combine it with other filters (for pagination) like so
<div ng-repeat="r in blueprints | orderBy:sortPty | startFrom:currentPage*pageSize | limitTo:pageSize | group:3">
<div ng-repeat="b in r">
I get the dreaded "10 $digest() iterations reached. Aborting!" error message again.
Here is my group filter:
filter('group', function() {
return function(input, size) {
if (input.grouped === true) {
return input;
}
var result=[];
var temp = [];
for (var i = 0 ; i < input.length ; i++) {
temp.push(input[i]);
if (i % size === 2) {
result.push(temp);
temp = [];
}
}
if (temp.length > 0) {
result.push(temp);
}
angular.copy(result, input);
input.grouped = true;
return input;
};
}).
Note both the use of angular.copy and the .grouped marker on input, but to no avail :(
I am aware of e.g. "10 $digest() iterations reached. Aborting!" due to filter using angularjs but obviously I did not get it.
Moreover, I guess the grouping logic is a bit naive, but that's another story. Any help would be greatly appreciated, as this is driving me crazy.
It looks like the real problem here is you're altering your input, rather than creating a new variable and outputing that from your filter. This will trigger watches on anything that is watching the variable you've input.
There's really no reason to add a "grouped == true" check in there, because you should have total control over your own filters. But if that's a must for your application, then you'd want to add "grouped == true" to the result of your filter, not the input.
The way filters work is they alter the input and return something different, then the next filter deals with the previous filters result... so your "filtered" check would be mostly irrelavant item in items | filter1 | filter2 | filter3 where filter1 filters items, filter2 filters the result of filter1, and filter3 filters the result of filter 2... if that makes sense.
Here is something I just whipped up. I'm not sure (yet) if it works, but it gives you the basic idea. You'd take an array on one side, and you spit out an array of arrays on the other.
app.filter('group', function(){
return function(items, groupSize) {
var groups = [],
inner;
for(var i = 0; i < items.length; i++) {
if(i % groupSize === 0) {
inner = [];
groups.push(inner);
}
inner.push(items[i]);
}
return groups;
};
});
HTML
<ul ng-repeat="grouping in items | group:3">
<li ng-repeat="item in grouping">{{item}}</li>
</ul>
EDIT
Perhaps it's nicer to see all of those filters in your code, but it looks like it's causing issues because it constantly needs to be re-evaluated on $digest. So I propose you do something like this:
app.controller('MyCtrl', function($scope, $filter) {
$scope.blueprints = [ /* your data */ ];
$scope.currentPage = 0;
$scope.pageSize = 30;
$scope.groupSize = 3;
$scope.sortPty = 'stuff';
//load our filters
var orderBy = $filter('orderBy'),
startFrom = $filter('startFrom'),
limitTo = $filter('limitTo'),
group = $filter('group'); //from the filter above
//a method to apply the filters.
function updateBlueprintDisplay(blueprints) {
var result = orderBy(blueprints, $scope.sortPty);
result = startForm(result, $scope.currentPage * $scope.pageSize);
result = limitTo(result, $scope.pageSize);
result = group(result, 3);
$scope.blueprintDisplay = result;
}
//apply them to the initial value.
updateBlueprintDisplay();
//watch for changes.
$scope.$watch('blueprints', updateBlueprintDisplay);
});
then in your markup:
<ul ng-repeat="grouping in blueprintDisplay">
<li ng-repeat="item in grouping">{{item}}</li>
</ul>
... I'm sure there are typos in there, but that's the basic idea.
EDIT AGAIN: I know you've already accepted this answer, but there is one more way to do this I learned recently that you might like better:
<div ng-repeat="item in groupedItems = (items | group:3 | filter1 | filter2)">
<div ng-repeat="subitem in items.subitems">
{{subitem}}
</div>
</div>
This will create a new property on your $scope called $scope.groupedItems on the fly, which should effectively cache your filtered and grouped results.
Give it a whirl and let me know if it works out for you. If not, I guess the other answer might be better.
Regardless, I'm still seeing the $digest error, which is puzzling: plnkr.co/edit/tHm8uYfjn8EJk3cG31DP – blesh Jan 22 at 17:21
Here is the plunker forked with the fix to the $digest error, using underscore's memoize function: http://underscorejs.org/#memoize.
The issue was that Angular tries to process the filtered collection as a different collection during each iteration. To make sure the return of the filter always returns the same objects, use memoize.
http://en.wikipedia.org/wiki/Memoization
Another example of grouping with underscore: Angular filter works but causes "10 $digest iterations reached"
You can use groupBy filter of angular.filter module,
and do something like this:
usage: (key, value) in collection | groupBy: 'property'or 'propperty.nested'
JS:
$scope.players = [
{name: 'Gene', team: 'alpha'},
{name: 'George', team: 'beta'},
{name: 'Steve', team: 'gamma'},
{name: 'Paula', team: 'beta'},
{name: 'Scruath', team: 'gamma'}
];
HTML:
<ul ng-repeat="(key, value) in players | groupBy: 'team'" >
Group name: {{ key }}
<li ng-repeat="player in value">
player: {{ player.name }}
</li>
</ul>
<!-- result:
Group name: alpha
* player: Gene
Group name: beta
* player: George
* player: Paula
Group name: gamma
* player: Steve
* player: Scruath

Resources