I can do this in Angular.js:
<tr ng-repeat="cust in customers">
<td>
{{ cust.name }}
</td>
</tr>
Which would iterate through all the customers by putting each in a <tr> of its own.
But what if I wanted two customers in one <tr>? How would I accomplish that?
I normally do that by messing around with indexes and modulus values, but I'm not sure how to do that here.
It turns out this can be done without any custom filters or changing the format of your data, though it does require some extra markup. I thought this woudn't be possible at first as you can't use a span or anything similar within the table for your ng-repeat. This answer however, points out that you can use a tbody.
So it's just a case of using the ng-if directive (which renders html if the expression is true), the $index variable (provided by ng-repeat) and the $even variable (which is also provided by ng-repeat and is true when $index is even
I've created a demo in this Plunker
<div ng-controller="MainCtrl">
<table>
<tbody ng-repeat="cust in customers">
<tr ng-if="$even">
<td>{{customers[$index].text}}</td>
<td>{{customers[$index+1].text}}</td>
</tr>
</tbody>
</table>
</div>
This would of course only work if you have two columns, what if you have more? Well you can also put a full expression into ng-if rather than just a variable. So you can use modulus values like this:
<tbody ng-repeat="cust in customers">
<tr ng-if="($index % 3) == 0">
<td>{{customers[$index].text}}</td>
<td>{{customers[$index+1].text}}</td>
<td>{{customers[$index+2].text}}</td>
</tr>
</tbody>
Related
I have an ng-repeat table with a handful of columns. One of te columns is an image field. Here is a snippet with the image column and one of the others:
<tbody>
<tr ng-repeat="insight in insights | orderBy:sort track by $index">
<td>
<select ng-model="insight.type" ng-change="setType(insight)" required="required">
<option value="INSIGHT">Insight</option>
<option value="EVIDENCE">Evidence</option>
</select>
</td>
<!--Image thing-->
<td>
<a ng-href="{{:: insight.imgurl}}" target="_blank"><img class="full" wb-lazy-load-image="{{:: insight.imgurl}}" data-height="600" data-width="800"></a>
</td>
...
</tbody>
As it stands, when I click to sort by a column, all of the columns sort with their associated row, except for the image row; it stays in the same spot.
If I remove the track by $index code at the <tr> it reorders all the columns as expected.
Any idea why or what I can do to fix that?
Edit:
FWIW - sort is defined in my Controller as: $scope.sort = ['-primary', '-live'];
Edit (Solution):
I ended up removing the one time binding as suggested in the accepted answer and it fixed the issue. However, after some consideration, we decided we'd rather have the performance gain of our one time, lazy loads than the track by performance as the lists would be small.
For anyone else who has this issue, one time binding with the images in conjuction with track by $index seemed to be the issue in this case.
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<body ng-app="orderByExample">
<script>
angular.module('orderByExample', [])
.controller('ExampleController', ['$scope', function($scope) {
$scope.friends =
[{name:'John', phone:'555-1212', age:10,sort:['-age','-name']},
{name:'Mary', phone:'555-9876', age:19,sort:['-age','-name']},
{name:'Mike', phone:'555-4321', age:21,sort:['-age','-name']},
{name:'Adam', phone:'555-5678', age:35,sort:['-age','-name']},
{name:'Julie', phone:'555-8765', age:29,sort:['-age','-name']}];
}]);
</script>
<div ng-controller="ExampleController">
<table class="friend">
<tr>
<th>Name</th>
<th>Phone Number</th>
<th>Age</th>
</tr>
<tr ng-repeat="friend in friends | orderBy:sort track by $index">
<td>{{friend.name}}</td>
<td>{{friend.phone}}</td>
<td>{{friend.age}}</td>
</tr>
</table>
</div>
</body>
According to the AngularJS Doc:
Note: track by must always be the last expression:
https://docs.angularjs.org/api/ng/directive/ngRepeat
An example which might help you:
<div ng-repeat="model in collection | orderBy: 'id' as filtered_result track by model.id">
{{model.name}}
</div>
It appears as though the one time binding syntax is the culprit. using {{::insight.imgurl}} would create a one time binding, where the watcher is removed from this element. However, when used in combination with wb-lazy-load-image, it would appear that the directive believes it is getting a two way binding but is in fact getting a one way binding.
My best guess without seeing how this directive works is that the directive takes the insight.imgurl and performs a network call to find the image. Then the filter causes the array to change order, but the directive isn't aware that the order has changed, due to the one way binding. Therefore, the directive loads the image in the incorrect place. Removing track by $index would cause the ng-repeat to be handled differently in the $digest loop, causing the problem directive to be completely re-rendered.
Removing :: from the binding to the directive input should allow the directive to function as intended, even without a full re-render. In general, :: is only intended for use in cases where the expressions will be permanent after rendering; in this case, they are not permanent due to the order filter.
I am setting the default orderby on a directive. On certain click events I'm changing the order by the column that was clicked upon. However when I try to set a default order by the index I get some really strange results.
I've created a Plunker based off the Angular Documentation. My goal is to have the default orderBy (only code shown here) perform no sorting so that I can change the orderBy property later.
Plunker: Plunker Or HTML Below
<div ng-controller="ExampleController">
<table class="friend">
<tr>
<th>Name</th>
<th>Phone Number</th>
<th>index</th>
</tr>
<tr ng-repeat="friend in friends | orderBy:'index'">
<td>{{friend.name}}</td>
<td>{{friend.phone}}</td>
<td>{{friends.indexOf(friend)}}</td>
</tr>
</table>
</div>
New Plunker. It appears as if this is a change from Angular 1.2 to 1.3 I tested working code on different versions of angular and in 1.2 you can pass a empty order by and it will order by the index but in 1.3 and 1.4 it sorts differently
You don't need any sorting at all here, the array is already in the order you want it to be. Assuming that you have a variable $scope.orderByField defining which property you want to use for ordering you could do something like:
<tr ng-repeat="friend in (orderByField ? (friends | orderBy : orderByField) : friends)">
So you would use the orderBy filter only if you really need it.
Plunker: http://plnkr.co/edit/kXOSF4adOohDDxLEtnGT?p=preview
Appart from that you can also define a custom comparator method and use it:
$scope.sortByIndex = function(a, b) {
return $scope.friends.indexOf(a) - $scope.friends.indexOf(b)
}
<tr ng-repeat="friend in friends | orderBy : sortByIndex">
But i wouldn't really recommend to use that, cause you will do unnecessary calculations.
I solved this issue by presorting my list at all times.
Then my function did all the work I was previously relying on Angular to do for me.
As an FYI I also submitted this as a bug to the angularJS team. They reported that it will be fixed in the next release (https://github.com/angular/angular.js/issues/12100)
I am new to Angular JS and I am trying to use ng-repeat to generate multiple row in my table, and in each row there should be a button to trigger a function with different parameter
<div ng-controller="testing">
<table>
<thead>
<tr>
<th>col 1</th>
<th>col 2</th>
<th>button</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="item in items">
<td>{{ item.prop1 }}</td>
<td>{{ item.prop2 }}</td>
<td><button ng-click="myFunction(item.id)">Trigger Function</button></td>
</tr>
</tody>
</table>
</div>
This of course doesn't work, as the ng-repeat creates its own scope, while myFunction is defined in the parent scope (i.e. the testing controller).
However, all I can find in Google result is the reason of not working, but lacks a way to fix the problem. How should I correct my code so that the ng-click works? Is there a way to define myFunction inside ng-repeat scope or is there a way to tell I want the function in the parent scope?
Change the ng-click to following
<button ng-click="testing.myFunction(item.id)">
Improve ng-repeat performance – using track by with ng-repeat
Pass $index to track your ng-repeat and which ng-click is being cliked
<tr ng-repeat="item in items track by $index">
<td>{{ item.prop1 }}</td>
<td>{{ item.prop2 }}</td>
<td><button ng-click="myFunction($index,item.id)">Trigger Function</button></td>
</tr>
For Reference
Probably one of the very first things you learned doing with Angular was how to use ngRepeat. It’s so easy and simple, you gotta love it. Here’s an example:
<ul class="tasks">
<li ng-repeat="task in tasks" ng-class="{done: task.done}">
{{task.id}}: {{task.title}}
</li>
</ul>
The problem
Now say you have a button above that list to refresh it.
The obvious implementation for this refresh would be something along these lines in your controller:
$scope.tasks = newTasksFromTheServer;
This is a trivial piece of code, but it would cause ngRepeat to remove all li elements of existing tasks and create them again, which might be expensive (e.g. we have a lot of them or each li’s template is complex). That means a lot of DOM operations.
Why would Angular do this? Behind the scenes ngRepeat adds a $$hashKey property to each task to keep track of it. If you replace the original tasks with new tasks objects from the server, even if those are in fact totally identical to your original tasks, they won’t have the $$hashKey property and so ngRepeat won’t know they represent the same elements.
track by to the rescue
In Angular 1.2 a new addition was made to the syntax of ngRepeat: the amazingly awesome track by clause. It allows you to specify your own key for ngRepeat to identify objects by, instead of just generating unique IDs.
This means that you can change the above to be ng-repeat="task in tasks track by task.id" and since the ID would be the same in both your original tasks and the updated ones from the server – ngRepeat will know not to recreate the DOM elements and reuse them. Boom.
you can also use ‘track by $index’ if your data source has duplicate data
//Below code will throw Error: dupes Duplicate Key in Repeater
<div ng-repeat="value in [4, 4]"></div>
//Below code will work perfectly fine
<div ng-repeat="value in [4, 4] track by $index"></div>
I have situation when i need to repeat multiple tbody in one table, what im trying to do is to make every tbody directive and i want its template to append to table, but when im put the directive inside the table tag its put his content outside the table.
the cart draw directive:
return {
restrict : 'AE',
templateUrl: 'client/cart/views/cart-draw.html',
scope : {},
replace: true,
controller : controller
}
the tpl:
<tbody ng-repeat="draw in CartService.items.draws track by $index">
<tr>
<td>
//some content
</td>
</tr>
</tbody>
the html:
<table class="table">
<cart-draw></cart-draw>
</table>
here is the plunker, if you inspect element you will see the tbody is out of the table:
http://plnkr.co/edit/9wEGFE5K0w0ayp6qo8Lx?p=preview
That is happening because the <table> tag doesn't recognize your custom <cart-draw> element as a valid child.
I would modify like so: http://plnkr.co/edit/u88N76h5dvLAvR3C1kRs?p=preview
index.html
<table><tbody cart-draw></tbody></table>
cart-draw.html
<tbody ng-repeat="body in bodies">
<tr>
<td>
{{body}}
</td>
</tr>
</tbody>
app.js
$scope.bodies = ["hello1", "hello2", "hello3"];
This is a long pending issue in Angular's Github repo.
https://github.com/angular/angular.js/issues/1459
I also stumbled upon to this problem once (with SVG). It happens because before rendering the directive, the template is cross verified with HTML DTD and alone doesn't make sense (without tag) and so it doesn't work. Same applies to <tr> and <li>
There are many solutions which uses ng-transclude and link functions to wrap it in respective parent tag and then use it.
This is actually a known & strange issue when it comes to directives & <table>'s.
I believe it actually comes in as invalid HTML at first, causing it somehow appear outside of your <table> tag.
Try making cart-draw an attribute of a <tbody>:
<table>
<tbody cart-draw></tbody>
</table>
plunker Example
This will make it work as intended.
I have a javascript list var myElements = [1, 2, 3] and want to get a string like the following one:
<th>
<tr>QC</tr>
<tr>1</tr>
<tr>2</tr>
<tr>3</tr>
</th>
As can been seen, the QC is not an element in the list but I want to insert that before the ng-repeat elements. It seems that I cannot do this by adding ng-repeat to the parent element (<th>). I tried to use ng-repeat-start and ng-repeat-end like this:
<th>
<tr>QC</tr>
<tr ng-repeat-start="myElement in myElements" ng-repeat-end ng-bind="myElement"></tr>
</th>
But it didn't work.(FIXED: it works.. but looks clumsy..)
Does anyone have ideas about how to do this in AngularJS? Thanks!
Have you tried with just ng-repeat?
<tr>
<th>QC</th>
<th ng-repeat="myElement in myElements" ng-bind="myElement"></th>
</tr>