Angular object property value change not propagated into view using ng-repeat - angularjs

I'm trying to generate a table using ng-repeat.
Use case
The data to generate the table from looks as follows:
$scope.data = [
{
name : 'foo1',
group : 1
},
{
name : 'foo2',
group : 1
},
{
name : 'foo3',
group : 1
},
{
name : 'foo4',
group : 1
},
{
name : 'foobar',
group : 2
},
{
name : 'foobarbar',
group : 3
}
];
The html generated should look like this:
<tr>
<th>Group</th>
<th>Name</th>
</tr>
<tr>
<td rowspan="4">1</td>
<td>foo1</td>
</tr>
<tr>
<td>foo2</td>
</tr>
<tr>
<td>foo3</td>
</tr>
<tr>
<td>foo4</td>
</tr>
<tr>
<td rowspan="1">2</td>
<td>foobar</td>
</tr>
<tr>
<td rowspan="1">2</td>
<td>foobarbar</td>
</tr>
Implementation
I know the easiest way would probably be to pre-process the data and group the items per group in a new array of arrays. However, I chose a different approach:
<td
ng-if = "isDifferentFromPrev(items, $index, groupingData)"
rowspan = "{{item._groupSize}}"
>
with
$scope.isDifferentFromPrev = function(array, index, groupingData){
if(index === 0){
groupingData.startI = 0;
groupingData.counter = 1;
array[0]._groupSize = 1;
return true;
}
var eq = equalsMethod(array[index], array[index-1]);
if(eq){
groupingData.counter++;
array[groupingData.startI]._groupSize = groupingData.counter;
}
else{
groupingData.startI = index;
groupingData.counter = 1;
array[index]._groupSize = 1;
}
return !eq;
};
Problem
For some reason the rendered value for rowspan is always 1.
The attribute is only set for the first td of the first tr of a group, as intended, but the value for it is 1.
If I put a breakpoint inside isDifferentFromPrev(), the values seem to be updated correctly. This does not reflect in the html though.
Solution?
It occured to me that maybe ng-repeat renders each step sequentially, without returning to it. So maybe the _groupSize values for the first item of each group do get properly updated, but since they are updated after that item has already been rendered by ng-repeat, the update isn't processed anymore.
I have no idea if this reasoning is correct, nor about how to solve it. Any suggestions please?

This solution, even if a bit orthodox, does work:
var app = angular.module("myApp", []);
app.controller("myController", function($scope) {
$scope.data = [{
name: 'foo1',
group: 1
}, {
name: 'foo2',
group: 1
}, {
name: 'foo3',
group: 1
}, {
name: 'foo4',
group: 1
}, {
name: 'foobar',
group: 2
}, {
name: 'foobarbar',
group: 3
}];
$scope.itemHasRowspan = function(item) {
return typeof item === "object" && item.hasOwnProperty("rowspan");
};
var groupData = {},
currentGroup = null,
addGroup = function(firstItem) {
currentGroup = firstItem.group;
groupData[firstItem.group] = {
"firstItem": firstItem,
"count": 1
};
};
angular.forEach($scope.data, function(item, index) {
if (item.group !== currentGroup) {
addGroup(item);
} else {
groupData[item.group].count++;
}
});
angular.forEach(groupData, function(group, index) {
group.firstItem["rowspan"] = group.count;
});
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="myApp">
<div ng-controller="myController">
<table>
<thead>
<tr>
<th>Group</th>
<th>Name</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="item in data">
<td ng-if="itemHasRowspan(item)" rowspan="{{ item.rowspan }}" valign="top">
{{ item.group }}
</td>
<td>
{{ item.name }}
</td>
</tr>
</tbody>
</table>
</div>
</div>

Related

AngularJS - two ng-repeats where first.id =second.id

I am trying to show name of city against the city id I receive from branches. What is the angular way of doing it?
$http.get(baseURL + '/city/getList?token=' + token)
.then(function(response) {
$scope.cities = response.data.data;
});
$http.get(baseURL + '/store/getAllBranch?token=' + token)
.then(function(response) {
$scope.branches= response.data.data;
});
<tbody>
<tr ng-repeat="x in branches| orderBy:'name'">
<td>{{x.name}}</td>
<td>{{x.address}}</td>
<td ng-repeat="city in cities track by x.city">{{city.name}}</td>
</tr>
</tbody>
Create a filter to convert cityID to cityName
<tbody>
<tr ng-repeat="x in branches| orderBy:'name'">
<td>{{x.name}}</td>
<td>{{x.address}}</td>
<td>{{x.cityId | cityName}}</td>
</tr>
</tbody>
app.filter('cityName', function(cityId) {
return function(cityId){
for(var ci = 0; ci < cities.length; ci++)
{
if( cities[ci].id == cityId )
return cities[ci].name;
}
return '';
};
});
And use it like that..
But this way is not good for performance. If you could declare related city names in related branches then you can just print that to screen with no calculation.
The 'angular way' is to construct an object which will be data-wise identical to how your table should look like and then you'd use ng-repeat to display the info.
You say you have the city id given in your branch. So you can just construct an object in which you push all your city names:
$scope.tableInfo = [];
for(var i = 0; i < branches.length; i++) {
var tableCell = {
name: branches[i].name,
address: branches[i].address,
city: branches.filter(function(element){
for(var city in cities) {
if(element.cityId === city.id) return city.name;
}
})[0];
};
}
That [0] is there because it'll always be a 1-element array(because of the filter function).
In the HTML, just use a <table> to show the tableInfo object.
<table>
<tr ng-repeat="tableCell in tableInfo">
<td>{{tableCell.name}}</td>
<td>{{tableCell.address}}</td>
<td>{{tableCell.city}}</td>
</tr>
</table>

How to filter in ngTables

I am using ng-table to generate my table.
but my data has two column, the first one is an object.
My function in controller :
$scope.allServers = function() {
$http.get("/volazi/getServers").success(function(data) {
$scope.serversDTO = data;
$scope.tableParams = new NgTableParams({}, {
dataset: data
});
});
}
So my data will be like:
[{
server {
name: "ser1",
date: "..",
group: {
name: "G1",
created: ".."
}
},
status
}, ...]
how i can use filter in html
<tr ng-repeat="sr in $data">
<td title="'Name'" filter="{server.name: 'text'}" sortable="'server.name'">
{{ sr.server.name }}
</td>
</tr>
Its not working like that
You should apply the filter to the loop:
<tr ng-repeat="sr in $data | filter: { server.name: 'text' }">
I solved th proble by adding ''
i replace
filter="{server.name: 'text'}"
by
filter="{'server.name': 'text'}"
This will be really very helpful :LINK

How to create table headers in a dynamically sized table using Angularjs

I want to create a dynamic height & width table, but I'm having trouble with the table header.
The <td> elements display just fine with this ng-repeater
<tr ng-repeat="set in currFormData.volume track by $index">
<td ng-repeat="repSpace in set track by $index">
{{repSpace}}
</td>
</tr>
but I want a <th> row containing 1, 2, 3, 4... and it must adjust as the table adjusts. I'm looking for something like this which looks through only the second level of my model 1 time:
<tr>
<th ng-repeat="repSpace in set in currFormData.volume track by $index">{{$index + 1}}</th>
</tr>
Here is my $scope model:
$scope.currFormData = {"date" : "", "volume": [
[[1,135],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],
[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],
[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],
[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]]
] };
How can I make this work? I'm open to changing my model.
Assuming that every 'set' in your 'volume' will be of equal length:
HTML:
<th ng-repeat="i in getArray(currFormData.volume[0].length) track by $index">{{$index + 1}}</th>
Controller:
$scope.getArray = function (length) {
return new Array(length);
}
Otherwise:
HTML:
<th ng-repeat="i in getLongestArray(currFormData.volume) track by $index">{{$index + 1}}</th>
Controller:
$scope.getLongestArray = function (arrayOfArrays) {
var longest: number = 0;
for (var i: number = 0; i < arrayOfArrays.length; i++) {
if (arrayOfArrays[i].length > longest) {
longest = arrayOfArrays[i].length;
}
}
return (new Array(longest));
}

How to sum in each row the values of before rows in specific column?

I've simulated my problem in this fiddle.
I have this HTML:
<table ng-app='Payments'>
<thead>
<tr>
<th>Date</th> <th>Cash</th> <th>Total</th>
</tr>
</thead>
<tbody ng-controller='paymentsController'>
<tr ng-repeat='pay in payments | orderBy : "date"'>
<td>{{pay.date}}</td>
<td><input type="textbox" ng-model="pay.cash"/></td>
<td></td>
</tr>
</tbody>
</table
And this JS:
var appModule = angular.module('Payments', []);
appModule.controller('paymentsController', function($scope) {
$scope.payments = [
{'id' : '1', 'date' : '2015-07-27', 'cash' : '149.98'},
{'id' : '2', 'date' : '2015-07-29', 'cash' : '70.00'},
{'id' : '3', 'date' : '2015-07-27', 'cash' : '129.99'},
{'id' : '4', 'date' : '2015-07-28', 'cash' : '349.90'}
];
});
How do I fill the third column with Angular?
The third column should be initially:
149.98 // due to 0.00 + 149.98
279.97 // due to 149.98 + 129.99
629.87 // due to 279.97 + 349.90
699.87 // due to 629.87 + 70.00
Then, ng-model should do the trick to update them automatically later.
Thanks in advance.
You could add a function to the scope to calculate the total at that index. You have to keep in mind that you are using order by which means you should us the as syntax to calculate the value from the ordered list.
CalculatePay function:
$scope.calculatePay = function(index) {
var sum = 0;
for(var i = 0; i <= index; i++) {
sum += parseFloat($scope.orderedList[i].cash);
}
return sum;
};
html:
<table ng-app='Payments'>
<thead>
<tr>
<th>Date</th> <th>Cash</th> <th>Total</th>
</tr>
</thead>
<tbody ng-controller='paymentsController'>
<tr ng-repeat="pay in payments | orderBy: 'date' as orderedList track by pay.id">
<td>{{pay.date}}</td>
<td><input type="textbox" ng-model="pay.cash"/></td>
<td>{{calculatePay($index)}}</td>
</tr>
</tbody>
</table>
track by is also helpful if the id is truely unique
Example: http://plnkr.co/edit/igBGY1h5RIKMNkncxhp6?p=preview
You would need to handle sorting in code, as the orderBy in ng-repeat creates a new list used for the display, and doesn't modify the original list. This would mean that the indexes of items in the display list don't match up with that in the original list. You'll also need a watcher on the payments collection to automatically update the totals at each position.
Something like
$scope.$watch('payments', function(newPayments) {
$scope.payments = orderByFilter($scope.payments, "date");
$scope.total = 0;
angular.forEach($scope.payments, function(payment) {
$scope.total += parseFloat(payment.cash);
payment.total = $scope.total;
});
}, true);
Fiddle: http://jsfiddle.net/29mh8bfe/4/

AngularJS orderby with array of arrays and separate keys

I have tabular data that I'm returning from the server in the form of an array of arrays for the data, and an array of keys associated with that data. Then, I want to sort by a particular key. Now, I know I can pre-process the data and zip together an array of objects, but say I don't want to do that. Is there an easy, built-in way to do this?
Some code that doesn't actually sort but does display the data. CodePen.
JS:
var app = angular.module('helloworld', []);
app.controller('TestController', function() {
this.headers = ['foo', 'bar'];
this.data = [
[ 'lol', 'wut' ],
[ '123', 'abc' ]
];
this.predicate = '';
});
HTML:
<table ng-app="helloworld" ng-controller="TestController as test">
<thead>
<tr>
<th ng-repeat="heading in test.headers" ng-click="test.predicate = heading">{{ heading }}</th>
</tr>
</thead>
<tbody>
<tr>
<td>Predicate:</td>
<td>{{ test.predicate }}</td>
</tr>
<tr ng-repeat="row in test.data | orderBy: test.predicate">
<td ng-repeat="column in row">{{ column }}</td>
</tr>
</tbody>
</table>
You can accomplish this but I would suggest that you instead have your server return you data as a list of json objects.
To sort your multidimensional array you basically sort by the inner array's index.
Your predicate would hold the index of the column you want to sort on (either 0 or 1 in your case)
<th ng-repeat="heading in test.headers"
ng-click="test.predicate = $index">
{{ heading }}
</th>
Create a sorting function in your controller as below:
this.sorter = function(item){
return item[test.predicate];
}
Apply this sorter as your orderBy expression as below:
<tr ng-repeat="row in data | orderBy: test.sorter">
I've forked and updated your CodePen for you: http://codepen.io/anon/pen/qvcKD
For reference, the solution where the array is zipped together with standard JS:
var app = angular.module('helloworld', []);
app.controller('TestController', function() {
this.headers = ['foo', 'bar'];
var data = [
[ 'lol', 'abc' ],
[ '123', 'wut' ]
];
this.data = [];
for (var i = 0, n = data.length; i < n; i++) {
this.data.push({});
for (var j = 0, m = this.headers.length; j < m; j++) {
this.data[i][this.headers[j]] = data[i][j];
}
}
this.predicate = '';
});
Or instead with LoDash as suggested by #Antiga:
_.each(data, function(item) {
this.data.push(_.zipObject(this.headers, item));
}, this);

Resources