Sorting elements in AngularJS for nested ng-repeat - angularjs

I am using bootstrap to display a grid of projects with each row having 3 columns. In order to accomplish this, I am using ng-repeat twice like below.
<div class="row" ng-repeat="chunk in projects">
<div class="col-sm-4" ng-repeat="project in chunk | orderBy:'title'">
{{project.title}}
</div>
</div>
I want to be able to sort the project based on its title field. Applying a filter only sorts a subset of the whole list i.e. sorts at the chunk level rather than the projects level.
var projects = [[{"title":"Z"},{"title":"A"},{"title":"M"}],
[{"title":"Z"},{"title":"A"},{"title":"M"}]];
After the orderBy happens, the rows are A M Z, A M Z. How do I get it to display A A M, M Z Z?
Here is the plunk for the above problem.
//EDIT : The above plunk points to the solution because I updated the plunk.

You'll need a custom filter that will turn your sub-arrays into a single array. The whole point of having separate arrays would be to isolate the contents, which is the opposite of what you want.
Filter:
yourAppModule
.filter('titleSort', function(){
return function(input){
var fullArray = []; //Will be the output array
for(var i=0;i<input.length;i++)
fullArray.concat(input[i]);
//Iterates through all the arrays in input (projects)
//and merges them into one array
fullArray = fullArray.sort(function(a,b){return a.title>b.title});
//Sorts it by the title property
var chunks = [];
var currentChunk;
for(var i=0;i<fullArray.length;i++){
if(i%3===0){
currentChunk = [];
chunks.push(currentChunk);
}
currentChunk.push(fullArray[i]);
}
return chunks;
};
} ...
Now your view can be this:
<div class="col-sm-4" ng-repeat="project in projects | titleSort">
{{project.title}}
</div>

I was able to solve this problem finally through filter chaining. I used angular-filter module/library but this can be even done without angular-filter.
<div class="container" ng-controller="ExampleController as exampleVm">
<div class="row" ng-repeat="chunk in exampleVm.projects| chunkBy: 'title' | groupBy : 3">
<div class="col-sm-4" ng-repeat="project in chunk">
{{project.title}}
</div>
</div>
I flattened the array and made it look like this.
var projects = [{"title:Z"},{"title":"A"},{"title":"M"},{"title:Z"},{"title":"A"},{"title":"M"}]
In the filter chain, I order the elements in the flattened array first by title and then divide it into subarrays of 3 elements each. Hope this helps.
You can find the plunk here.

try this. may be help you.
var app = angular.module("app",[])
app.controller('ctrl',['$scope', function($scope){
$scope.data = [[{"title":"Z"},{"title":"A"},{"title":"M"}],
[{"title":"Z"},{"title":"A"},{"title":"M"}]];
$scope.data2 = [];
angular.forEach( $scope.data,function(d){
angular.forEach(d,function(d1){
$scope.data2.push(d1);
})
})
}]);
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.22/angular.min.js"></script>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css">
<div ng-app="app" ng-controller="ctrl">
<div class="item item-checkbox">
<div class="row">
<div class="col-sm-4" ng-repeat="project in data2 | orderBy:'title'">
{{project.title}}
</div>
</div>
</div>

Related

data-ng-repeat: how to set different options according to the index

Sorry if this look like a stupid question but I have just started using angular.js and certain things are not easy to catch.
I have a list of numbers which I am showing using this:
<li data-ng-repeat="i in getNumber(list) track by $index" data-ng-click="myFunction($index)" data-ng-class="myClass">{{$index+1}}</li>
This is showing my list as 1 2 3 4 5 etc
I'd like to change this to show something different upon reaching a specific index, i.e.
if index == 5 then show ABCD instead of {{$index+1}}
Is it something I can achieve with ng-repeat or do I need to use a different approach?
Thanks a lot for the input
You can achieve it inside the interpolation markup {{...}} only, as we can do all normal javascript operations inside it:
var app = angular.module('myApp', []);
app.controller('AppCtrl', function($scope) {});
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.7.5/angular.min.js"></script>
<section ng-app="myApp">
<div ng-controller="AppCtrl">
<li data-ng-repeat="i in [1,2,3,4,5,6,7] track by $index">
{{ $index === 5 ? 'ABCD' : $index+1 }}
</li>
</div>
</section>

ng-if and ng-repeat to compare in table

I have a table with id column (1,2,3,4,5) and i have an array of elements. $scope.elements has values - 1,3.
<ul ng-repeat="x in elements>
<li>{{x}}</li>
</ul>
Using this I have to compare the values inside elements array with id in a table. If both idd matches I have to hide that row.
When I use ng-if, since I use ng-repeat multiple rows are getting created for each iteration...
can anyone help me to solve this issue...
UPDATE:
hides2 = [1,3,4]
ng-repeat="x in [hides2] track by $index" ng-if="x!=3"
But the table shows the values, (its not hiding 1,3,4 records)
[enter image description here]1
I don't knnow how you are using ng-if but it's very simple to use, here's is an simple example
angular.module("app",[]);
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.0/angular.min.js"></script>
<body ng-app="app">
<div ng-repeat="item in [1,2,3,4,5,6,1,2,4,5] track by $index" ng-if="item !== 1">{{item}}</div>
Here try this.. create a function in controller as following
$scope.elements = [1,2,3,4,5];
$scope.checkMatch = function(id){
if($scope.elements.indexOf(id) !== -1){
return true;
}else{
return false;
}
}
In you template call this method in ng-if as following and pass the id in checkMatch...
<ul ng-repeat="x in elements>
<li ng-if="checkMatch(x)">{{x}}</li>
</ul>

AngularJS sort People into Alphabetical List and Filter

In AngularJS I have an Object with keys that are letters of the alphabet and each key is an array of people.
<div ng-repeat="(letter, group) in people"></div>
<div ng-repeat="people in letter"></div>
</div>
This is to create something like this in HTML:
A. Arnold, Avery, Adam, Alex
B. Bob, Boris
C. Chris, Connor, Caleb
How would I go about filtering each individual field by name? For example applying this.
<div ng-repeat="people in letter | filter:{'name':search.query}">
Works fine, however, you get a result like this when you type "A" in:
A. Arnold, Avery, Adam, Alex
B.
C.
How can I collapse B and C? Is there a better organization method for this data?
The trick here is to store your filtered names in a scope variable so you can do a length check to hide the empty result.
You can do this with the following markup:
<div ng-repeat="people in filtered = (group | filter: search)"></div>
Then add ng-if="filtered.length > 0" to the tag you want to hide if result is 0.
Please have a look at the demo below or in this jsfiddle.
angular.module('demoApp', [])
.controller('mainController', function($scope) {
$scope.peoples = {
A: ['Arnold', 'Avery', 'Adam', 'Alex'],
B: ['Bob', 'Boris'],
C: ['Chris', 'Connor', 'Caleb']
};
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.4.4/angular.js"></script>
<div ng-app="demoApp" ng-controller="mainController">
<input ng-model="search" placeholder="enter search string"></input>
<div ng-repeat="(letter, group) in peoples">
<strong ng-if="filtered.length > 0">{{letter}}</strong>
<div ng-repeat="people in filtered = (group | filter: search)">{{people}}</div>
<br ng-if="filtered.length > 0"/>
</div>
</div>

How does orderBy work with $odd and $even in AngularJS?

I have two columns both containing items from the same array. I want to achieve the masonry effect since the height of each .tile will be different.
<div class="col-md-6">
<div class="tile" ng-repeat="item in items| orderBy: 'id'" ng-if="$odd">
<button ng-click="alert($index)"></button>
</div>
</div>
<div class="col-md-6">
<div class="tile" ng-repeat="item in items| orderBy: 'id'" ng-if="$even">
<button ng-click="alert($index)"></button>
</div>
</div>
Will Angular do the odd-even alternation on the sorted array or the way elements are stored in the array?
Angular is going to alternate on the way the elements are stored in the array. If you want to organize the array I would use .sort() before assigning to Angular $scope.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort
Based on the trying the code below, it seems that Angular does apply $odd and $even after sorting based on the orderBy filter. However, this would only work correctly in a single ng-repeat directive. I would modify your code to look like the following:
angular.module("myApp", [])
.controller("ItemController", function($scope) {
$scope.items = [{
id: 50
}, {
id: 1
}, {
id: 2
}, {
id: 5
}, {
id: 3
}];
$scope.alert = function(idx) {
console.log(idx);
};
});
<link href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.1/css/bootstrap.min.css" rel="stylesheet"/>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<body ng-app="myApp">
<div class="col-md-6" ng-controller="ItemController">
<div class="tile" ng-repeat="item in items| orderBy: 'id'">
<button class="btn btn-danger" ng-if="$odd" ng-click="alert($index)">{{item.id}}</button>
<button class="btn btn-success" ng-if="$even" ng-click="alert($index)">{{item.id}}</button>
</div>
</div>
</body>
In other words, you should apply the ng-if directive to the element inside your ng-repeat directive. Depending on what exactly you want to alternate, it might be simpler to use ng-class or another directive that allows you to specify conditional attributes.
The Angular orderBy filter returns a copy of the source array. The ng-repeat is applied to the copy.
Your example case will work, but it will create twice as many $watch elements, and you might experience odd flickering of the data shifting back and forth between columns if the number of rows changes. Also, because arrays are 0 based, your rows will be backwards.

ng-repeat to index into array

I have an angularjs app that streams data via ajax, and I would like to ng-repeat over the data. I have the data streaming and displaying but now I want to templatize the objects. The issue I am running into is that I am using ng-repeat simply to index into an array in the controller. I now need to have a
<div class="row" data-ng-repeat="row in rows">
<div class="span3" data-ng-repeat="col in cols">
//displays the raw json fine
{{ data[$parent.$index * numColumns + $index] }}
// also displays the raw json
{{ item = data[$parent.$index * numColumns + $index] }}
<div>Id: {{item.Id}} </div>
<div>Title: {{ item.ClientJobTitle }}</div>
...
</div>
</div>
I could always repeat the array index expression for each property, but there will be a couple dozen properties, so the code will be ugly and all the repeated calculations will slow things down.
What is the right (angular) way to do this?
Update
I need it to be responsive too, I will be adjusting the number of columns on the fly based on window width.
Update
I guess what I really want is something like the following non-working example
<div class="row" data-ng-repeat="row in rows">
<div class="span3" data-ng-repeat="col in cols">
<div ng-model="data[$parent.$index * numColumns + $index]">
<!-- Here $model would refer to the ng-model above -->
<div>Id: {{$model.Id}} </div>
<div>Title: {{ $model.Title }}</div>
...
</div>
</div>
</div>
You've probably handled the issue some way, but it's worth a mention that your non-working example is absolutely possible, even without directives:
var app = angular.module('app',[])
app.controller('myCtrl', function($scope) {
$scope.numColumns = 2
$scope.rows = [1,2,3]
$scope.cols = [1,2]
$scope.data = [
{Id:'abc',Title:'cde'},
{Id:'qwe',Title:'rty'},
{Id:'asd',Title:'fgh'},
{Id:'foo',Title:'bar'},
{Id:'uni',Title:'corn'},
{Id:'mag',Title:'ic'},
];
$scope.change1 = function(){
$scope.numColumns = 2
$scope.rows = [1,2,3]
$scope.cols = [1,2]
}
$scope.change2 = function(){
$scope.numColumns = 3
$scope.rows = [1,2]
$scope.cols = [1,2,3]
}
});
.span3 {
display:inline-block;
padding:5px;
margin:5px;
background: pink;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.5.5/angular.min.js"></script>
<body ng-app="app">
<div ng-controller="myCtrl">
<button ng-click="change1()">2x3</button>
<button ng-click="change2()">3x2</button>
<div class="row" data-ng-repeat="row in rows">
<div class="span3" data-ng-repeat="col in cols">
<div>
{{($model = data[$parent.$index * numColumns + $index])?'':''}}
<!-- Here $model would refer to the ng-model above -->
<div>Id: {{$model.Id}} </div>
<div>Title: {{ $model.Title }}</div>
...
</div>
</div>
</div>
</div>
</body>
basically, it's possible to create such variable as "$model" on the fly, inside expression:
{{($model = data[$parent.$index * numColumns + $index])?'':''}}
It won't be printed itself, it'll be responsive, working inside ng-repeat and all. Still, it's kind of workaround, and directive might be a better choice.
I think the way to do this would be to simply loop through the items themselves, and ditch all the numRows and numColumns.
http://plnkr.co/edit/Qu6X5FnZY3TpgyNjnBXy?p=preview
That is outputting exactly what yours is doing, with much simpler angularjs code.
It seems that you are using the row/column counts to limit how many items you display on a row horizontally. I think you can handle this with pure css, you can see in my plunk I'm limiting the width on a parent container. I'm doing it manually, but you could easily attach a dynamic css class to the parent container and have more flexible control over how many items appear on a row.

Resources