ng-repeat to index into array - angularjs

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.

Related

Sorting elements in AngularJS for nested ng-repeat

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>

How to create div and assign object to dataset dynamically using Angularjs

I have below div (class name 'sp') which I would like to dynamically create based on the sk from a dataset object.
div code
<div style="" class="swindow">
<div class="sp" style="">
<div class="svis" style="">
<div style="height:95%;width:95%;margin-top:0px;margin-left:5px;">
<chart dsn="dyndata" editable="false" labelled="true"></chart>
</div>
</div>
<div class="sdata" style="">
<div class="stext" style="">Average:78% </div>
</div>
</div>
While searching for similar examples, I came across ng-repeat in Angular js and I thought it might suit for this kind of objective.
But am very new to Angular js and not sure how to assign the data to my dyndata variable dynamically and create new div (class=sp) for each of the given id.
Here is the lookup object
[
{"id":20,"st":[{"label":"Audi","value":10},{"label":"BMW","value":70}]},
{"id":26,"st":[{"label":"Benz","value":40},{"label":"BMW","value":20}]},
{"id":12,"st":[{"label":"AUDI","value":60},{"label":"Tesla","value":70}]},
{"id":57,"st":[{"label":"MZ","value":30},{"label":"Honda","value":40}]}
]
When I input the id's as a set [12,26,57] - Three divs (each for #sp) should get created one for each of ids. In those, each div should have the dyndata assigned with the respective 'st' from above javascript object.
I could create div's in jquery using .append function to the container (#swindow) each time when I need. But am not sure how to assign sk as input to dyndata dataset for each div that gets created.
Could you please share how this can be achieved using Angular js ?
Here is the angular js code I used -
<script>
var app = angular.module('ExampleApp', ['ui.plot']);
app.controller('PlotCtrl', function ($scope) {
$scope.dyndata={};
});
</script>
I'm not exactly sure what you're trying to do but I think it should look something like this. Here is a plnkr..
Controller:
app.controller('PlotCtrl', function($scope) {
$scope.items = [
{"id":20,"st":[{"label":"Audi","value":10},{"label":"BMW","value":70}]},
{"id":26,"st":[{"label":"Benz","value":40},{"label":"BMW","value":20}]},
{"id":12,"st":[{"label":"AUDI","value":60},{"label":"Tesla","value":70}]},
{"id":57,"st":[{"label":"MZ","value":30},{"label":"Honda","value":40}]}
]
});
HTML:
<body ng-controller="PlotCtrl">
<div style="" class="swindow">
<div class="sp" style="" ng-repeat="item in items">
<div class="svis" style="">
<strong>{{item.id}}:</strong>
<div>-{{item.st[0].label}}</div>
<div>-{{item.st[1].label}}</div>
<div style="height:95%;width:95%;margin-top:0px;margin-left:5px;">
<chart dsn="dyndata" editable="false" labelled="true"></chart>
</div>
</div>
<div class="sdata" style="">
<div class="stext" style="">Average:78% </div>
<br>
</div>
</div>
</div>
</body>

Live search in AngularJS: updating the results

I want a live search: the results are queried from web api and updated as the user types.
The problem is that the list flickers and the "No results" text appears for a fraction of second, even if the list of results stays the same. I guess I need to remove and add items with special code to avoid this, calculating differences between arrays, etc.
Is there a simpler way to avoid this flicker at least, and probably to have possibility to animate the changes?
It looks like this now:
The html part is:
<div class="list-group">
<a ng-repeat="test in tests track by test.id | orderBy: '-id'" ng-href="#/test/{{test.id}}" class="list-group-item">
<h4 class="list-group-item-heading">{{test.name}}</h4>
{{test.description}}
</a>
</div>
<div ng-show="!tests.length" class="panel panel-danger">
<div class="panel-body">
No tests found.
</div>
<div class="panel-footer">Try a different search or clear the text to view new tests.</div>
</div>
And the controller:
testerControllers.controller('TestSearchListCtrl', ['$scope', 'TestSearch',
function($scope, TestSearch) {
$scope.tests = TestSearch.query();
$scope.$watch('search', function() {
$scope.tests = TestSearch.query({'q':$scope.search});
});
}]);
You should use ng-animate module to get the classes you need for smooth animation. For each ng-repeat item that's moved, added, or removed - angular will add specific classes. Then you can style those classes via CSS or JS so they don’t flicker.
An alternative way of doing what you require is to use the angular-ui bootstrap Typeahead component (check at the bottom of the post). It has a type-ahead-wait property in milliseconds and also a template url for customising it.
<div ng-app>
<div ng-controller="MyController">
<input type="search" ng-model="search" placeholder="Search...">
<button ng-click="fun()">search</button>
<ul>
<li ng-repeat="name in names">{{ name }}</li>
</ul>
<p>Tips: Try searching for <code>ann</code> or <code>lol</code>
</p>
</div>
</div>
function MyController($scope, $filter) {
$scope.names = [
'Lolita Dipietro',
'Annice Guernsey',
'Gerri Rall',
'Ginette Pinales',
'Lon Rondon',
'Jennine Marcos',
'Roxann Hooser',
'Brendon Loth',
'Ilda Bogdan',
'Jani Fan',
'Grace Soller',
'Everette Costantino',
'Andy Hume',
'Omar Davie',
'Jerrica Hillery',
'Charline Cogar',
'Melda Diorio',
'Rita Abbott',
'Setsuko Minger',
'Aretha Paige'];
$scope.fun = function () {
console.log($scope.search);
$scope.names = $filter('filter')($scope.names, $scope.search);
};
}

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.

Horizontal ng-repeat(er) with new row breaks

Working with Bootstrap and AngularJS, is there a way to ng-repeat horizontally with a new row for every set amount of elements?
I have been playing around with ng-class to accomplish this Fiddle, but the problem is that I can't get the float left divs within the initial row div... Any thoughts, am I not thinking of something or would this best be done with a Directive?
Here is my code (live example in the above fiddle link):
<body ng-app="myApp">
<div ng-controller="MyCtrl">
<div ng-repeat="num in numbers"
ng-class="{'row': ($index)%2==0, 'col-md-6': ($index)%2!=0}">
<div ng-class="{'col-md-6': ($index)%2==0}">
{{num}}
</div>
</div>
</div>
</body>
var myApp = angular.module('myApp', [])
.controller('MyCtrl', function ($scope) {
$scope.numbers = ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10"];
});
.row {
clear: both;
width: 100%;
border: 1px solid red;
}
.col-md-6 {
width: 50%;
float: left;
border: 1px solid blue;
}
If you are working with Bootstrap 3 and AngularJS you can declare a new filter that will return one array of sub array slices and then do two ng-repeat.
It will look like that:
<div class="row" ng-repeat="row in filtered = (arr | splitArrayFilter:3)">
<div class="col-md-4" ng-repeat="n in row">
<h3>{{n}}</h3>
</div>
</div>
app.filter('splitArrayFilter', function() {
return function(arr, lengthofsublist) {
if (!angular.isUndefined(arr) && arr.length > 0) {
var arrayToReturn = [];
var subArray=[];
var pushed=true;
for (var i=0; i<arr.length; i++){
if ((i+1)%lengthofsublist==0) {
subArray.push(arr[i]);
arrayToReturn.push(subArray);
subArray=[];
pushed=true;
} else {
subArray.push(arr[i]);
pushed=false;
}
}
if (!pushed)
arrayToReturn.push(subArray);
console.log(JSON.stringify(arrayToReturn));
return arrayToReturn;
}
}
});
You can Find it on Plunker here: http://plnkr.co/edit/rdyjRtZhzHjWiWDJ8FKJ?p=preview
for some reason the view in plunker does not support bootstrap 3 columns but if you open it in embedded view or in browsers you can see that it works.
It was clever what you were doing with ng-class. I hadn't ever thought of using %2 within the expression there.
But for future reference, there is a slightly easier way to accomplish that: ng-class-even and ng-class-odd. It does the same thing as what you were doing, but just a bit cleaner:
<div ng-repeat="num in numbers" ng-class-even="'md-col-6'" ng-class-odd="'row'">
{{num}}
</div>
But this doesn't resolve your problem. If I understand you correctly, you want a row, with two columns within that row. The easiest way I could think of is to split up the arrays. Put the repeat on the div, then have 2 span within the div. I think one of the issues that you had originally, is that you were repeating a single div, and trying to treat that block element as an inline
Controller
myApp.controller('MyCtrl', function ($scope) {
$scope.evens = ["2","4","6","8","10","12","14"];
$scope.odds = ["1","3","5","7","9","11","13"];
});
HTML
<div ng-controller="MyCtrl">
<div ng-repeat="odd in odds" class="row">
<span class="span3">{{odd}}</span>
<span class="span2">{{evens[$index]}}</span>
</div>
</div>
Fiddle
Being that you're using version 1.1.5, that also opens you up to a new directive: ng-if! You could also use ng-switch to do some conditional logic displays.
You didn't include bootstrap in your fiddle, and for some reason I can't get jsFiddle to display bootstrap. So I created some temp CSS classes that would somewhat resemble bootstraps class="span"
No need to add .row class .. I did this:
HTML:
<div ng-repeat="product in allProducts">
<div class="my-col-50">
<h1>{{product.title}}</h1>
</div>
</div>
CSS:
.my-col-50{float:left;width:50%;}
and it's work like a charm.
Although this isn't the "proper" way of doing this, there is a way to achieve this using CSS.
For example, this is for a 3 column layout:
HTML:
<div class="row">
<div ng-repeat="(key, pod) in stats.pods" class="pod-wrap">
<div ng-if="objectCheck(pod) == false" class="col-md-4 col-sm-4 pod">
<div>
<h2 ng-bind="key"></h2>
<p class="total" ng-bind="pod | number"></p>
</div>
</div>
<div ng-if="objectCheck(pod) == true" class="col-md-4 col-sm-4 pod">
<div>
<h2 ng-bind="key"></h2>
<div ng-repeat="(type, value) in pod track by $index">
<p class="status"><% type %> <small><% value %></small></p>
</div>
</div>
</div>
</div>
</div>
CSS:
.pod-wrap:nth-of-type(3n):after {
display: table;
content: '';
clear: both;
}
I tried two of the suggestions given here...
the one by yshaool works fine but like i commented on it give me that infinite loop error.
Then I tried something like below:
<div class="row" ng-repeat="row in split($index, 3, row.Attempts)">
<div class="col-md-4" ng-repeat="attempt in row">
<div>Attempt {{row.AttemptNumber}}</div>
<div>{{row.Result}}</div>
</div>
</div>
and the function:
$scope.split = function (index, length, attempts) {
var ret = attempts.slice(index * length, (index * length) + length);
console.log(JSON.stringify(ret));
return ret;
}
was going somewhere with that when i realized that it could be as simple as
<div ng-repeat="attempt in row.Attempts">
<div class="col-md-4">
<div>Attempt {{attempt.AttemptNumber}}</div>
<div>{{attempt.Result}}</div>
</div>
</div>
using "col-md-4" does the trick as I only need to split using three columns per row..
(let bootstrap do the work!)
anyway the other answers here were really useful...
Depending upon the number of columns that you need in your template, create chunks of the original data source in your controller.
$scope.categories = data //contains your original data source;
$scope.chunkedCategories = [] //will push chunked data into this;
//dividing into chunks of 3 for col-4. You can change this
while ($scope.categories.length > 0)
$scope.chunkedCategories.push($scope.categories.splice(0, 3));
In your template you can now do the following
<div class="row" ng-repeat="categories in chunkedCategories">
<div class="col-xs-4" ng-repeat="category in categories">
<h2>{{category.title}}</h2>
</div>
</div>
My approach was to use the $index variable, which is created and updated by AngularJS within an ng-repeat directive, to trigger a call to the CSS clearfix hack which, in turn, resets to a new row.
I am using the following versions: AngularJS 1.5.5 and Bootstrap 3.3.4
<!-- use bootstrap's grid structure to create a row -->
<div class="row">
<!-- Cycle through a list: -->
<!-- use angular's ng-repeat directive -->
<div ng-repeat="item in itemList">
<!-- Start a new row: -->
<!-- use the css clearfix hack to reset the row -->
<!-- for every item $index divisible by 3. -->
<!-- note that $index starts at 0. -->
<div ng-if="$index % 3 == 0" class="clearfix"></div>
<!-- Create a column: -->
<!-- since we want 3 "item columns"/row, -->
<!-- each "item column" corresponds to 4 "Bootstrap columns" -->
<!-- in Bootstrap's 12-column/row system -->
<div class="col-sm-4">
{{item.name}}
</div>
</div>
</div>
To keep solution bootstrap formated i solved this using ng-class
<div ng-repeat="item in items">
<div ng-class="{ 'row': ($index + 1) % 4 == 0 }">
<div class="col-md-3">
{{item.name}}
</div>
</div>
</div>

Resources