Different row for each unit - arrays

I have a nested array list and I would like to get a different table row for every unit.
JSON:
[
{
"id":1,
"units":[
{
"id":1,
"name":"Test 1",
},
{
"id":2,
"name":"Test 2"
}
]
}
]
So Test 1 and Test 2 should be in different table rows.
HTML:
<tr ng-repeat="user in users">
<td><p ng-repeat="unit in user.units">{{unit.name}}</p></td>
</tr>
The above code works but I am getting unit names in the same row.
I tried:
<tr ng-repeat="unit in users.units">
<td>{{unit.name}}</td>
</tr>
But that doesn't work. Any help will be appreciated!

Here's a solution that won't require any changes to your data, though it may not be the cleanest solution. The other option (which I'm not going to write up) is to modify your data and flatten the data down to a single array or object containing all the units.
I'm going to make an assumption that you want your DOM (including the <table>) to ideally look like this:
<table>
<tr>
<td><p>Test 1</p></td>
</tr>
<tr>
<td><p>Test 2</p></td>
</tr>
</table>
The problem here is there isn't anything that represents the "users" part of that first ng-repeat (2 ng-repeats are needed for this data structure). As far as I'm aware it's not possible to start an ng-repeat without also creating a DOM element (at least a temporary one), so we can create some valid html (<caption>s in this case) and have angular exclude it using a one-time binding ng-if.
<div ng-controller="MyCtrl">
<table>
<!-- The ng-if here will remove the <caption> from the end result. It's just a placeholder for the users repeater. -->
<caption ng-repeat-start="user in users" ng-if="::false"></caption>
<tr ng-repeat="unit in user.units">
<td><p>{{unit.name}}</p></td>
</tr>
<caption ng-repeat-end ng-if="::false"></caption>
</table>
</div>

Related

how to iterate json data using ng-repeat in angularjs

how to iterate json data using ng-repeat in angularjs
{
"CSS Corp":{
"COE":{"win_loss":[6,4]},
"YNOS":{"win_loss":[5,5]},
"ESTEE":{"win_loss":[10,0]},
"ELC":{"win_loss":[8,2]}
},
"SSSPL":{
"PEG":{"win_loss":[0,10]},
"ARUBA":{"win_loss":[2,8]},
"SALES":{"win_loss":[1,9]},
"MARKETING":{"win_loss":[7,3]}
},
}
Your question is very broad. You will first have to attach those JSON data to the scope of your controller and expose it to the template through a variable myData. Assuming you know how to do that, the use of ng-repeat becomes very trivial (add more columns or rows to fit your dataset):
<table>
<thead>
<tr><th>Header 1</th></tr>
</thead>
<tbody>
<tr ng-repeat="item in myData">
<td>{{ item }}</td>
</tr>
</tbody>
</table>
Use ng-repeat
Syntax : <tr ng-repeat="value in container">
Assuming that you have a array of JSON object in your JS as below,
var arrayObject = [
"CSS Corp":{
"COE":{"win_loss":[6,4]},
"YNOS":{"win_loss":[5,5]},
"ESTEE":{"win_loss":[10,0]},
"ELC":{"win_loss":[8,2]}
},
"SSSPL":{
"PEG":{"win_loss":[0,10]},
"ARUBA":{"win_loss":[2,8]},
"SALES":{"win_loss":[1,9]},
"MARKETING":{"win_loss":[7,3]}
}
];
Then your view should iterate as below,
<div ng-repeat="company in arrayObject">
{{company}} // use whatever you want to print
</div>

ng-repeat dynamically create arrays for nested ng-repeats

Is it possible to have ng-repeat dynamically create arrays for nested ng-repeats?
I know this sounds silly, but I'm essentially looking for something like this, and hoping someone will tell me how terrible of an idea this is and present a better solution:
<tbody ng-repeat="row in myRows track by $index">
<tr>{{row.name}}</tr>
<tr ng-repeat="subRow in myRows$index>{{subRow.name}}</tr>
</tbody>
So the idea is that the first <tr> row actually has a button that will show the subRows once clicked. So once clicked (before it actually displays the rows), I'll create the array then, such as:
myRows0 = [{name:"Sub Row A", value:1},
{name:"Sub Row B", value:2}];
or if the second row was clicked, I'd create:
myRows1 = [{name:"Sub Row C", value:3},
{name:"Sub Row D", value:4}];
I'm assuming something like this won't work because ng-repeat needs to have the array created before it can create the DOM. Is that correct? But I'm not sure how else I'd be able to create something like this then using this table structure. Any help?
It is possible and in my opinion it is not a bad idea, for example you may want to load your subRow data only when user clicks on displaySubRow items if your subRow data is big or they are images, to avoid putting an unnecessary burden to your server or keep your users waiting.
Working Plunker
Sample Code
html
<table>
<tbody ng-repeat="item in data" ng-init="item.show = false">
<tr>
<td>
<button ng-show="item.show==false" ng-click="getRowSubItems(item)">Show</button>
<button ng-show="item.show==true" ng-click="item.show = false">Hide</button>
</td>
<td>
{{item.name}}
</td>
</tr>
<tr ng-show="item.show == true">
<td>
</td>
<td>
<div ng-repeat="subItem in item.cars">
{{subItem}}
</div>
</td>
</tr>
</tbody>
</table>
js
$scope.data =
[{"name":"Lex",
"age":43
},
{"name":"Alfred",
"age":30
},
{"name":"Diana",
"age":35
},
{"name":"Bruce",
"age":27
},
{"name":"Oliver",
"age":32
}];
$scope.getRowSubItems = function(item){
//you can also make a http call to get data from your server
item.show = true;
if(item.name == "Lex"){
$http.get('https://jsonplaceholder.typicode.com/posts/1')
.then(function(response) {
item.cars = response.data;
});
}
else{
item.cars = [ "Ford", "BMW", "Fiat" ];
}
}

Nested ng-repeat on same element without flattening original data source

I am using ng-repeat to generate table rows for the following object
$scope.things = [{
name: 'Bob',
otherThings: [{
foo: 'hello'
},{
foo: 'bye'
}]
},{
name: 'Sid',
otherThings: [{
foo: 'cake'
},{
foo: 'fish'
}]
}];
The ng-repeat expression I have gone with is:
<tbody ng-repeat="thing in things">
<tr ng-repeat="otherThing in thing.otherThings">
<td>{{thing.name}}</td>
<td>{{otherThing.foo}}</td>
</tr>
</tbody>
This generates multiple tbody elements, one per parent item in my object:
<tbody>
<tr>
<td>Bob</td>
<td>hello</td>
</tr>
<tr>
<td>Bob</td>
<td>bye</td>
</tr>
</tbody>
<tbody>
<tr>
<td>Sid</td>
<td>cake</td>
</tr>
<tr>
<td>Sid</td>
<td>fish</td>
</tr>
</tbody>
I want to avoid multiple tbody elements, but I know in order to access both parent and child items, I will have to nest the ng-repeat's.
I could flatten the original object so that the parent information is duplicated for each child, but I want to avoid this if possible and use the data structure as is.
Are there any special uses of ng-repeat, e.g. multiple expressions that could be used on one ng-repeat to give access to both parent and child items in each iteration?
Thanks
UPDATE
The reason I want to avoid multiple tbody elements is because I have a css selector for alternate row shading:
tbody tr:nth-child(odd){
background-color: $theme-list-alt;
}
If each parent only had one child item they would all get coloured the same, whereas I want alternate row colours across throughout.
Update 2
I wondered whether using ng-repeat-start and ng-repeat-end could help me here, so I tried with the following Plunkr
This doesnt give me a row per child, e.g:
Bob hello
Bob bye
Sid cake
Sid fish
I can see why, just not sure how I can achieve this.
Try this.
<tbody>
<tr ng-repeat="thing in things">
<td>{{thing.name}}</td>
<td ng-repeat="otherThing in thing.otherThings">{{otherThing.foo}}</td>
</tr>
</tbody>

ng-repeat over a div not working

I have used ng-repeat numerous times already in the past, but for some reason I cannot yet understand why it is not on the following situation:
I have an array of objects called registers which I am referencing on the ng-repeat but nothing happens.
I know the array is populated because I have seen it on numerous console.log and because it works if I move the ng-repeat over to the <tbody>
<div ng-repeat = "r in registers">
<!-- START HEADER -->
<tbody class="js-table-sections-header">
<tr>
<td class="text-center">
<i class="fa fa-angle-right"></i>
</td>
<td class="font-w600">Denise Watson</td>
</tr>
</tbody> <!-- END HEADER -->
<tbody>
<tr>
<td class="text-center"></td>
<td>
<!-- Summernote Container -->
<div class="js-summernote-air">
<p>End of air-mode area!</p>
</div>
</td>
</tr>
</tbody>
<!-- END TABLE -->
</div>
I was hoping someone could tell me if there is something I may be ignoring.
Thanks in advance.
I think I just ran into this same problem. It stems from <div> not being a valid elment within a <table>.
I'm guessing that since you have <tbody> there, that there is a <table> tag that was left out of your snippet. <div>s aren't allowed in a table, so the browser moves it before the table. In my situation, doing this, causes the angular scope to change so that there was nothing to iterate over. You can verify this by using the developer tools of your browser.
So, my guess is that you probably want to move the ng-repeat onto the <tbody> or <table> tag.
If you want to use ng-repeat in "div" tag means use "span"
inside div tag. instead of using "table" and its sub attributes..
else use your ng-repeat inside "table" or "thead" or "tr" also
it will iterate rows ...
than only ng-repeat will works.

How to use ng-repeat without an html element

I need to use ng-repeat (in AngularJS) to list all of the elements in an array.
The complication is that each element of the array will transform to either one, two or three rows of a table.
I cannot create valid html, if ng-repeat is used on an element, as no type of repeating element is allowed between <tbody> and <tr>.
For example, if I used ng-repeat on <span>, I would get:
<table>
<tbody>
<span>
<tr>...</tr>
</span>
<span>
<tr>...</tr>
<tr>...</tr>
<tr>...</tr>
</span>
<span>
<tr>...</tr>
<tr>...</tr>
</span>
</tbody>
</table>
Which is invalid html.
But what I need to be generated is:
<table>
<tbody>
<tr>...</tr>
<tr>...</tr>
<tr>...</tr>
<tr>...</tr>
<tr>...</tr>
<tr>...</tr>
</tbody>
</table>
where the first row has been generated by the first array element, the next three by the second and the fifth and sixth by the last array element.
How can I use ng-repeat in such a way that the html element to which it is bound 'disappears' during rendering?
Or is there another solution to this?
Clarification: The generated structure should look like below. Each array element can generate between 1-3 rows of the table. The answer should ideally support 0-n rows per array element.
<table>
<tbody>
<!-- array element 0 -->
<tr>
<td>One row item</td>
</tr>
<!-- array element 1 -->
<tr>
<td>Three row item</td>
</tr>
<tr>
<td>Some product details</td>
</tr>
<tr>
<td>Customer ratings</td>
</tr>
<!-- array element 2 -->
<tr>
<td>Two row item</td>
</tr>
<tr>
<td>Full description</td>
</tr>
</tbody>
</table>
As of AngularJS 1.2 there's a directive called ng-repeat-start that does exactly what you ask for. See my answer in this question for a description of how to use it.
Update: If you are using Angular 1.2+, use ng-repeat-start. See #jmagnusson's answer.
Otherwise, how about putting the ng-repeat on tbody? (AFAIK, it is okay to have multiple <tbody>s in a single table.)
<tbody ng-repeat="row in array">
<tr ng-repeat="item in row">
<td>{{item}}</td>
</tr>
</tbody>
If you use ng > 1.2, here is an example of using ng-repeat-start/end without generating unnecessary tags:
<html>
<head>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<script>
angular.module('mApp', []);
</script>
</head>
<body ng-app="mApp">
<table border="1" width="100%">
<tr ng-if="0" ng-repeat-start="elem in [{k: 'A', v: ['a1','a2']}, {k: 'B', v: ['b1']}, {k: 'C', v: ['c1','c2','c3']}]"></tr>
<tr>
<td rowspan="{{elem.v.length}}">{{elem.k}}</td>
<td>{{elem.v[0]}}</td>
</tr>
<tr ng-repeat="v in elem.v" ng-if="!$first">
<td>{{v}}</td>
</tr>
<tr ng-if="0" ng-repeat-end></tr>
</table>
</body>
</html>
The important point: for tags used for ng-repeat-start and ng-repeat-end set ng-if="0", to let not be inserted in the page. In this way the inner content will be handled exactly as it is in knockoutjs (using commands in <!--...-->), and there will be no garbage.
You might want to flatten the data within your controller:
function MyCtrl ($scope) {
$scope.myData = [[1,2,3], [4,5,6], [7,8,9]];
$scope.flattened = function () {
var flat = [];
$scope.myData.forEach(function (item) {
flat.concat(item);
}
return flat;
}
}
And then in the HTML:
<table>
<tbody>
<tr ng-repeat="item in flattened()"><td>{{item}}</td></tr>
</tbody>
</table>
The above is correct but for a more general answer it is not enough. I needed to nest ng-repeat, but stay on the same html level, meaning write the elements in the same parent.
The tags array contain tag(s) that also have a tags array.
It is actually a tree.
[{ name:'name1', tags: [
{ name: 'name1_1', tags: []},
{ name: 'name1_2', tags: []}
]},
{ name:'name2', tags: [
{ name: 'name2_1', tags: []},
{ name: 'name2_2', tags: []}
]}
]
So here is what I eventually did.
<div ng-repeat-start="tag1 in tags" ng-if="false"></div>
{{tag1}},
<div ng-repeat-start="tag2 in tag1.tags" ng-if="false"></div>
{{tag2}},
<div ng-repeat-end ng-if="false"></div>
<div ng-repeat-end ng-if="false"></div>
Note the ng-if="false" that hides the start and end divs.
It should print
name1,name1_1,name1_2,name2,name2_1,name2_2,
I would like to just comment, but my reputation is still lacking. So i'm adding another solution which solves the problem as well. I would really like to refute the statement made by #bmoeskau that solving this problem requires a 'hacky at best' solution, and since this came up recently in a discussion even though this post is 2 years old, i'd like to add my own two cents:
As #btford has pointed out, you seem to be trying to turn a recursive structure into a list, so you should flatten that structure into a list first. His solution does that, but there is an opinion that calling the function inside the template is inelegant. if that is true (honestly, i dont know) wouldnt that just require executing the function in the controller rather than the directive?
either way, your html requires a list, so the scope that renders it should have that list to work with. you simply have to flatten the structure inside your controller. once you have a $scope.rows array, you can generate the table with a single, simple ng-repeat. No hacking, no inelegance, simply the way it was designed to work.
Angulars directives aren't lacking functionality. They simply force you to write valid html. A colleague of mine had a similar issue, citing #bmoeskau in support of criticism over angulars templating/rendering features. When looking at the exact problem, it turned out he simply wanted to generate an open-tag, then a close tag somewhere else, etc.. just like in the good old days when we would concat our html from strings.. right? no.
as for flattening the structure into a list, here's another solution:
// assume the following structure
var structure = [
{
name: 'item1', subitems: [
{
name: 'item2', subitems: [
],
}
],
}
];
var flattened = structure.reduce((function(prop,resultprop){
var f = function(p,c,i,a){
p.push(c[resultprop]);
if (c[prop] && c[prop].length > 0 )
p = c[prop].reduce(f,p);
return p;
}
return f;
})('subitems','name'),[]);
// flattened now is a list: ['item1', 'item2']
this will work for any tree-like structure that has sub items. If you want the whole item instead of a property, you can shorten the flattening function even more.
hope that helps.
for a solution that really works
html
<remove ng-repeat-start="itemGroup in Groups" ></remove>
html stuff in here including inner repeating loops if you want
<remove ng-repeat-end></remove>
add an angular.js directive
//remove directive
(function(){
var remove = function(){
return {
restrict: "E",
replace: true,
link: function(scope, element, attrs, controller){
element.replaceWith('<!--removed element-->');
}
};
};
var module = angular.module("app" );
module.directive('remove', [remove]);
}());
for a brief explanation,
ng-repeat binds itself to the <remove> element and loops as it should, and because we have used ng-repeat-start / ng-repeat-end it loops a block of html not just an element.
then the custom remove directive places the <remove> start and finish elements with <!--removed element-->
<table>
<tbody>
<tr><td>{{data[0].foo}}</td></tr>
<tr ng-repeat="d in data[1]"><td>{{d.bar}}</td></tr>
<tr ng-repeat="d in data[2]"><td>{{d.lol}}</td></tr>
</tbody>
</table>
I think that this is valid :)

Resources