I have following markup
<div ng-app>
<table ng-controller="test">
<tbody>
<tr ng-repeat="row in rows">
<td ng-repeat="col in cols">
<input type="text" ng-model="row[col].val" />
</td>
</tr>
</tbody>
</table>
</div>
controller looks like this
function test($scope) {
$scope.cols = ["col1", "col2", "col3"];
$scope.rows = [{
col1: { val: "x"}
}, {
col2: { val: "y" }
}]
}
When i try to set column value that does not yet exist, i get
"Cannot set property 'val' of undefined".
Example is here http://jsfiddle.net/z9NkG/2/
In documentation is note:
ngModel will try to bind to the property given by evaluating the
expression on the current scope. If the property doesn't already exist
on this scope, it will be created implicitly and added to the scope.
But it fails when using indexer instead of property name.
Is there a way to define ngModel expression dynamically or am i misusing angular?
And what about:
<input type="text" ng-model="row[col].val" ng-init="row[col] = row[col] || {}" />
Fiddle: http://jsfiddle.net/z9NkG/8/
I'm not sure about your model, but I think you are using a 2 dimensional array. I'm oversimplifying, not knowing,
make cols collection a child or rowsL
function test($scope) {
$scope.colnames = ["col1", "col2", "col3"];
$scope.rows = [{
cols: { col1: "x"}, { col2: "y"}
}, {
cols: { col1: "z"}, { col2: "a"}
}]
}
And then use nested ng-repeats
<table>
<tbody>
<tr ng-repeat="row in rows">
<td ng-repeat="col in row.cols">
<input type="text" ng-model="col.val" />
</td>
</tr>
</tbody>
</table>
The main problem is that you're setting an undefined property of an undefined object as the input's model. Angular does indeed automatically create the corresponding variables for your model binding, but to do so, you would have to bind the input directly to the row[col] property instead of row[col].val :
<input type="text" ng-model="row[col]" />
Here's the live demo: http://jsfiddle.net/z9NkG/9/
try this
make changes in controller
add below code
$scope.cols.forEach(function(x, y) {
angular.forEach($scope.rows, function(i, j) {
if ((i[x])== null)
i[x] = {};
});
})
yo can refer fiddle http://jsfiddle.net/z9NkG/10/
Related
I am defining a custom filter like so:
<div class="idea item" ng-repeat="item in items" isoatom>
<div class="section comment clearfix" ng-repeat="comment in item.comments | range:1:2">
....
</div>
</div>
As you can see the ng-repeat where the filter is being used is nested within another ng-repeat
The filter is defined like this:
myapp.filter('range', function() {
return function(input, min, max) {
min = parseInt(min); //Make string input int
max = parseInt(max);
for (var i=min; i<max; i++)
input.push(i);
return input;
};
});
I'm getting:
Error: Duplicates in a repeater are not allowed. Repeater: comment in item.comments | range:1:2 ngRepeatAction#https://ajax.googleapis.com/ajax/libs/angularjs/1.1.4/an
The solution is actually described here: http://www.anujgakhar.com/2013/06/15/duplicates-in-a-repeater-are-not-allowed-in-angularjs/
AngularJS does not allow duplicates in a ng-repeat directive. This means if you are trying to do the following, you will get an error.
// This code throws the error "Duplicates in a repeater are not allowed.
// Repeater: row in [1,1,1] key: number:1"
<div ng-repeat="row in [1,1,1]">
However, changing the above code slightly to define an index to determine uniqueness as below will get it working again.
// This will work
<div ng-repeat="row in [1,1,1] track by $index">
Official docs are here: https://docs.angularjs.org/error/ngRepeat/dupes
For those who expect JSON and still getting the same error, make sure that you parse your data:
$scope.customers = JSON.parse(data)
I was having an issue in my project where I was using ng-repeat track by $index but the products were not getting reflecting when data comes from database. My code is as below:
<div ng-repeat="product in productList.productList track by $index">
<product info="product"></product>
</div>
In the above code, product is a separate directive to display the product.But i came to know that $index causes issue when we pass data out from the scope. So the data losses and DOM can not be updated.
I found the solution by using product.id as a key in ng-repeat like below:
<div ng-repeat="product in productList.productList track by product.id">
<product info="product"></product>
</div>
But the above code again fails and throws the below error when more than one product comes with same id:
angular.js:11706 Error: [ngRepeat:dupes] Duplicates in a repeater are not allowed. Use 'track by' expression to specify unique keys. Repeater
So finally i solved the problem by making dynamic unique key of ng-repeat like below:
<div ng-repeat="product in productList.productList track by (product.id + $index)">
<product info="product"></product>
</div>
This solved my problem and hope this will help you in future.
What do you intend your "range" filter to do?
Here's a working sample of what I think you're trying to do: http://jsfiddle.net/evictor/hz4Ep/
HTML:
<div ng-app="manyminds" ng-controller="MainCtrl">
<div class="idea item" ng-repeat="item in items" isoatom>
Item {{$index}}
<div class="section comment clearfix" ng-repeat="comment in item.comments | range:1:2">
Comment {{$index}}
{{comment}}
</div>
</div>
</div>
JS:
angular.module('manyminds', [], function() {}).filter('range', function() {
return function(input, min, max) {
var range = [];
min = parseInt(min); //Make string input int
max = parseInt(max);
for (var i=min; i<=max; i++)
input[i] && range.push(input[i]);
return range;
};
});
function MainCtrl($scope)
{
$scope.items = [
{
comments: [
'comment 0 in item 0',
'comment 1 in item 0'
]
},
{
comments: [
'comment 0 in item 1',
'comment 1 in item 1',
'comment 2 in item 1',
'comment 3 in item 1'
]
}
];
}
If by chance this error happens when working with SharePoint 2010: Rename your .json file extensions and be sure to update your restService path. No additional "track by $index" was required.
Luckily I was forwarded this link to this rationale:
.json becomes an important file type in SP2010. SP2010 includes certains webservice endpoints. The location of these files is 14hive\isapi folder. The extension of these files are .json. That is the reason it gives such a error.
"cares only that the contents of a json file is json - not its file extension"
Once the file extensions are changed, should be all set.
Just in case this happens to someone else, I'm documenting this here, I was getting this error because I mistakenly set the ng-model the same as the ng-repeat array:
<select ng-model="list_views">
<option ng-selected="{{view == config.list_view}}"
ng-repeat="view in list_views"
value="{{view}}">
{{view}}
</option>
</select>
Instead of:
<select ng-model="config.list_view">
<option ng-selected="{{view == config.list_view}}"
ng-repeat="view in list_views"
value="{{view}}">
{{view}}
</option>
</select>
I checked the array and didn't have any duplicates, just double check your variables.
Duplicates in a repeater are not allowed. Use 'track by' expression to specify unique keys.
Repeater: {0}, Duplicate key: {1}, Duplicate value: {2}
Example
<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta charset="utf-8" />
<title></title>
<script src="angular.js"></script>
</head>
<body>
<div ng-app="myApp" ng-controller="personController">
<table>
<tr> <th>First Name</th> <th>Last Name</th> </tr>
<tr ng-repeat="person in people track by $index">
<td>{{person.firstName}}</td>
<td>{{person.lastName}}</td>
<td><input type="button" value="Select" ng-click="showDetails($index)" /></td>
</tr>
</table> <hr />
<table>
<tr ng-repeat="person1 in items track by $index">
<td>{{person1.firstName}}</td>
<td>{{person1.lastName}}</td>
</tr>
</table>
<span> {{sayHello()}}</span>
</div>
<script> var myApp = angular.module("myApp", []);
myApp.controller("personController", ['$scope', function ($scope)
{
$scope.people = [{ firstName: "F1", lastName: "L1" },
{ firstName: "F2", lastName: "L2" },
{ firstName: "F3", lastName: "L3" },
{ firstName: "F4", lastName: "L4" },
{ firstName: "F5", lastName: "L5" }]
$scope.items = [];
$scope.selectedPerson = $scope.people[0];
$scope.showDetails = function (ind)
{
$scope.selectedPerson = $scope.people[ind];
$scope.items.push($scope.selectedPerson);
}
$scope.sayHello = function ()
{
return $scope.items.firstName;
}
}]) </script>
</body>
</html>
If you call a ng-repeat within a < ul> tag, you may be able to allow duplicates. See this link for reference.
See Todo2.html
Duplicates in a repeater are not allowed. Use 'track by' expression to specify unique keys. Repeater: sdetail in mydt, Duplicate key: string: , Duplicate value:
I faced this error because i had written wrong database name in my php api part......
So this error may also occurs when you are fetching the data from database base, whose name is written incorrect by you.
My JSON response was like this:
{
"items": [
{
"index": 1, "name": "Samantha", "rarity": "Scarborough","email": "maureen#sykes.mk"
},{
"index": 2, "name": "Amanda", "rarity": "Vick", "email": "jessica#livingston.mv"
}
]
}
So, I used ng-repeat = "item in variables.items" to display it.
In following table there can be more than one list on particular date. So For list column there can be more than one list for given date.
I am able to insert more than one value in single cell but it shift the row in which I inserted more than one value, Please look at DEMO.
Example: Table
Date...............List
12/1/2016 .... python, angularjs
13/1/2016..... java, html
data:
$scope.todolists = [{
date: '12/1/2016',
list: {python, angularjs}
}, {
date: '13/1/2016',
list: {java, html}
}];
view:
<tbody>
<tr ng-repeat="todolist in todolists" >
<td>{{todolist.date}}</td>
<td ng-repeat="list in todolist">{{subject}}</td>
</tr>
</tbody>
I tried ng-repeat inside ng-repeat but it is not working. So my question is how to insert more than one value in single cell in table.
Your references are wrong.
<tbody>
<tr ng-repeat="todolist in todolists" >
<td>{{todolist.date}}</td>
<td ng-repeat="list in todolist.list">{{list}}</td>
</tr>
</tbody>
or, preferably,
<tbody>
<tr ng-repeat="todolist in todolists" >
<td>{{todolist.date}}</td>
<td>
<span ng-repeat="list in todolist.list">
{{list + ($last ? "" : ", ") }}
</span>
</td>
</tr>
</tbody>
Your JSON is invalid. If you want to store values without properties, you need to use Array but not Object. Also, if the values are string you need to wrap them with comma like: ['val1', 'val2', ...];
You can do ng-repeat inside ng-repeat but you need to iterate the right property todolist.list.
You can't use {{subject}} if you have not property subject in you model. So you need to use {{list}} when you do the ng-repeat like ng-repeat="list in todolist.list".
The full code:
angular.module('app', []).
controller('ctrl', function($scope) {
$scope.todolists = [{ date: '12/1/2016', list: ['python', 'angularjs'] }, { date: '13/1/2016', list: ['java', 'html'] }];
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="app" ng-controller="ctrl">
<table>
<tr ng-repeat="todolist in todolists">
<td>{{todolist.date}}</td>
<td ng-repeat="list in todolist.list">{{list}}</td>
</tr>
</table>
</div>
Your datas are not properly declared. It should be:
$scope.todolists = [{
date: '12/1/2016',
list: [
'python',
'angularjs'
]
}, {
date: '13/1/2016',
list: [
'java',
'html'
]
}];
Note the '' around python, etc. and the list should be a list => [] and not {}.
A nice way to go is to write a custom filter:
angular.module("myApp", [])
.filter('nicelist', function() {
return function(input) {
if (input instanceof Array) {
return input.join(",");
}
return input;
}
});
Then, you can use :
<table>
<tr ng-repeat="todolist in todolists">
<td>{{todolist.date}}</td>
<td>{{todolist.list | nicelist}}</td>
</tr>
</table>
Here is a working fiddle.
In my angular app I'm trying to display JSON data in a table. The data looks like this:
$scope.data =
{
"EVENT NAME":"Free Event",
"ORDER ID":311575707,
"DATE":"6/26/14",
"GROSS REVENUE (USD)":"0",
"TICKET REVENUE (USD)":"0",
"EVENTBRITE FEES (USD)":"0",
"CC PROCESSING (USD)":"0",
"TICKETS":1,
"TYPE":"Free Order",
"STATUS":"Free Order",
"TRANSACTION ID":"",
"NOTES":"",
"FIRST NAME":"Khee Seng",
"LAST NAME":"Chua",
"EMAIL ADDRESS":"email#anemailadderss.com"
};
And I'm displaying it like this:
<table class="table table-striped selector">
<tbody>
<tr>
<td ng-repeat="(key, value) in data">
<strong>{{key}}</strong>
</td>
</tr>
<tr>
<td ng-repeat="(key, value) in data">
{{value}}
</td>
</tr>
</tbody>
</table>
In my mind this should go through each `(key, value) pair in the object and display it in order. However, AngularJS displays the values in alphabetical order.
Here's a plunkr which replicates this issue: http://plnkr.co/edit/V3Y2ZuwV1v9Pzsl0jGhA?p=preview
How can I tweak the code so it displays in the natural order that the object actually comes in?
You can achieve it like this
Working Demo
In the scope define a method like as shown
$scope.notSorted = function(obj){
if (!obj) {
return [];
}
return Object.keys(obj);
}
and in html like as shown below
html
<table class="table table-striped selector">
<tbody>
<tr>
<th ng-repeat="key in notSorted(data)">
{{key}}
</th>
</tr>
<tr>
<td ng-repeat="key in notSorted(data)" ng-init="value = data[key]">
{{value}}
</td>
</tr>
</tbody>
</table>
Original Article: ng-repeat with no sort? How?
A Javascript object does not have the concept of 'natural order' of its keys:
Definition of an Object from ECMAScript Third Edition (here):
4.3.3 Object
An object is a member of the type Object. It is an unordered collection of properties
each of which contains a primitive value, object, or function [...]
You probably should change a bit your data structure...
For example:
$scope.data =
{
1: { "EVENT NAME": "Free Event" },
2: { "ORDER ID": 311575707 },
/* ... */
};
And then use the numerical key to sort your items...
Object properties don't have a natural order.
You can achieve what you're looking for with a slightly different Object:
$scope.data =
{
columns: [
{
"EVENT NAME":"Free Event",
"priority": 0
},
{
"ORDER_ID":311575707,
"priority": 1
},
...
]
}
I would like to ask you if you can give me a hand on this.
I have created a jsfiddle with my problem here. I need to generate dynamically some inputs with ng-model in a ng-repeater using the way ng-model="my_{{$index}}".
In jsfiddle you can see that everything it's working fine until I try to generate it dynamically.
The html would be:
<div ng-app>
<div ng-controller="MainCtrl">
<table border="0" cellpadding="0" cellspacing="0" width="100%">
<tr>
<td>
<select ng-model="selectedQuery"
ng-options="q.name for q in queryList" >
<option title="---Select Query---" value="">---Select Query---</option>
</select>
</td>
</tr>
<tr ng-repeat="param in parameters">
<td>{{param}}:</td>
<td><input type="text" ng-model="field_X" />field_{{$index}}</td>
</tr>
</table>
<div>
<div>
And the javascript...
function MainCtrl($scope) {
$scope.queryList = [
{ name: 'Check Users', fields: [ "Name", "Id"] },
{ name: 'Audit Report', fields: [] },
{ name: 'Bounce Back Report', fields: [ "Date"] }
];
$scope.$watch('selectedQuery', function (newVal, oldVal) {
$scope.parameters = $scope.selectedQuery.fields;
});
}
Can you give me any idea?
Thanks a lot.
Does it solve your problem?
function MainCtrl($scope) {
$scope.queryList = [
{ name: 'Check Users', fields: [ "Name", "Id"] },
{ name: 'Audit Report', fields: [] },
{ name: 'Bounce Back Report', fields: [ "Date"] }
];
$scope.models = {};
$scope.$watch('selectedQuery', function (newVal, oldVal) {
if ($scope.selectedQuery) {
$scope.parameters = $scope.selectedQuery.fields;
}
});
}
And in your controller:
<tr ng-repeat="param in parameters">
<td>{{param}}:</td>
<td><input type="text" ng-model="models[param] " />field_{{$index}}</td>
</tr>
Fiddle
What you could do is to create an object on a scope (say, values) and bind to the properties of this object like so:
<input type="text" ng-model="values['field_' + $index]" />
Here is a jsFiddle illustrating the complete solution: http://jsfiddle.net/KjsWL/
I did elaborate my answer from pkozlowski's and try to generate a dynamic form, with dynamic ng-model:
<form ng-submit="testDynamic(human)">
<input type="text" ng-model="human.adult[($index+1)].name">
<input type="text" ng-model="human.adult[($index+1)].sex">
<input type="text" ng-model="human.adult[($index+1)].age">
</form>
But first, we need to define the 'human' scope inside our controller
$scope.human= {};
And then, on submission we will have the data like this (depending on how much field is generated):
var name = human.adult[i].name;
var sex = human.adult[i].sex;
var age = human.adult[i].age;
It's pretty straightforward and I hope my answer helps.
Is there a reason to generate those field names? Can you treat each field as an object with name and value instead of a string name? (FIDDLE)
function MainCtrl($scope) {
$scope.queryList = [
{ name: 'Check Users', fields: [ { name: "Name" }, { name: "Id" } ] },
{ name: 'Audit Report', fields: [] },
{ name: 'Bounce Back Report', fields: [ { name: "Date" } ] }
];
}
And just repeat off of selectedQuery.fields:
<tr ng-repeat="field in selectedQuery.fields">
<td>{{field.name}}:</td>
<td><input type="text" ng-model="field.value" /></td>
</tr>
Beterraba's answer was very helpful for me. However, when I had to migrate the solution to Typescript it wouldn't behave for me. Here is what I did instead. I expanded the individual parameters (fields on the queryList in your example) into full objects that included a "value" field. I then bound to the "value" field and it worked great!
Originally you had:
[
{ name: 'Check Users', fields: [ "Name", "Id"] },
...
}
]
I changed it to something like this:
[
{ name: 'Check Users', fields: [
{Text:"Name",Value:""},
{Text:"Id",Value:0}],
...
]
}
]
...and bound to the 'Value' sub-field.
Here is my Typescript if you care.
In the html:
<div ng-repeat="param in ctrl.sprocparams" >
<sproc-param param="param" />
</div>
In the sproc-param directive that uses Angular Material. See where I bind the ng-model to param.Value:
return {
restrict: 'E',
template: `
<md-input-container class="md-block" flex-gt-sm>
<label>{{param.Name}}</label>
<input name="{{param.Name}}" ng-model=param.Value></input>
</md-input-container>`,
scope: {
param: "="
}
}
I need to generate dynamically some inputs with ng-model in a ng-repeater using the way ng-model="my_{{$index}}".
One can use the this identifier with a bracket notation property accessor:
<tr ng-repeat="param in parameters">
<td>{{param}}:</td>
<td>
<input type="text" ng-model="this['my_'+$index]" />
my_{{$index}}
</td>
</tr>
It is possible to access the $scope object using the identifier this.
For more information, see
AngularJS Developer Guide - Expression Context
I'm looking for a way to add rows to a table. My data structure looks like that:
rows = [
{ name : 'row1', subrows : [{ name : 'row1.1' }, { name : 'row1.2' }] },
{ name : 'row2' }
];
I want to create a table which looks like that:
table
row1
row1.1
row1.2
row2
Is that possible with angular js ng-repeat? If not, what would be a "angular" way of doing that?
Edit:
Flatten the array would be a bad solution because if i can iterate over the sub elements i could use different html tags inside the cells, other css classes, etc.
More than one year later but found a workaround, at least for two levels (fathers->sons).
Just repeat tbody's:
<table>
<tbody ng-repeat="row in rows">
<tr>
<th>{{row.name}}</th>
</tr>
<tr ng-repeat="sub in row.subrows">
<td>{{sub.name}}</td>
</tr>
</tbody>
</table>
As far as I know all browsers support multiple tbody elements inside a table.
More than 3 years later, I have been facing the same issue, and before writing down a directive I tried this out, and it worked well for me :
<table>
<tbody>
<tr ng-repeat-start="row in rows">
<td>
{{ row.name }}
</td>
</tr>
<tr ng-repeat-end ng-repeat="subrow in row.subrows">
<td>
{{ subrow.name }}
</td>
</tr>
</tbody>
</table>
You won't be able to do this with ng-repeat. You can do it with a directive, however.
<my-table rows='rows'></my-table>
Fiddle.
myApp.directive('myTable', function () {
return {
restrict: 'E',
link: function (scope, element, attrs) {
var html = '<table>';
angular.forEach(scope[attrs.rows], function (row, index) {
html += '<tr><td>' + row.name + '</td></tr>';
if ('subrows' in row) {
angular.forEach(row.subrows, function (subrow, index) {
html += '<tr><td>' + subrow.name + '</td></tr>';
});
}
});
html += '</table>';
element.replaceWith(html)
}
}
});
I'm a bit surprised that so many are advocating custom directives and creating proxy variables being updated by $watch.
Problems like this are the reason that AngularJS filters were made!
From the docs:
A filter formats the value of an expression for display to the user.
We aren't looking to manipulate the data, just format it for display in a different way. So let's make a filter that takes in our rows array, flattens it, and returns the flattened rows.
.filter('flattenRows', function(){
return function(rows) {
var flatten = [];
angular.forEach(rows, function(row){
subrows = row.subrows;
flatten.push(row);
if(subrows){
angular.forEach(subrows, function(subrow){
flatten.push( angular.extend(subrow, {subrow: true}) );
});
}
});
return flatten;
}
})
Now all we need is to add the filter to ngRepeat:
<table class="table table-striped table-hover table-bordered">
<thead>
<tr>
<th>Rows with filter</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="row in rows | flattenRows">
<td>{{row.name}}</td>
</tr>
</tbody>
</table>
You are now free to combine your table with other filters if desired, like a search.
While the multiple tbody approach is handy, and valid, it will mess up any css that relies on the order or index of child rows, such as a "striped" table and also makes the assumption that you haven't styled your tbody in a way that you don't want repeated.
Here's a plunk: http://embed.plnkr.co/otjeQv7z0rifPusneJ0F/preview
Edit:I added a subrow value and used it in the table to show which rows are subrows, as you indicated a concern for being able to do that.
Yes, it's possible:
Controller:
app.controller('AppController',
[
'$scope',
function($scope) {
$scope.rows = [
{ name : 'row1', subrows : [{ name : 'row1.1' }, { name : 'row1.2' }] },
{ name : 'row2' }
];
}
]
);
HTML:
<table>
<tr ng-repeat="row in rows">
<td>
{{row.name}}
<table ng-show="row.subrows">
<tr ng-repeat="subrow in row.subrows">
<td>{{subrow.name}}</td>
</tr>
</table>
</td>
</tr>
</table>
Plunker
In case you don't want sub-tables, flatten the rows (while annotating subrows, to be able to differentiate):
Controller:
function($scope) {
$scope.rows = [
{ name : 'row1', subrows : [{ name : 'row1.1' }, { name : 'row1.2' }] },
{ name : 'row2' }
];
$scope.flatten = [];
var subrows;
$scope.$watch('rows', function(rows){
var flatten = [];
angular.forEach(rows, function(row){
subrows = row.subrows;
delete row.subrows;
flatten.push(row);
if(subrows){
angular.forEach(subrows, function(subrow){
flatten.push( angular.extend(subrow, {subrow: true}) );
});
}
});
$scope.flatten = flatten;
});
}
HTML:
<table>
<tr ng-repeat="row in flatten">
<td>
{{row.name}}
</td>
</tr>
</table>
Plunker
Here is an example. This code prints all names of all the people within the peopeByCity array.
TS:
export class AppComponent {
peopleByCity = [
{
city: 'Miami',
people: [
{
name: 'John', age: 12
}, {
name: 'Angel', age: 22
}
]
}, {
city: 'Sao Paulo',
people: [
{
name: 'Anderson', age: 35
}, {
name: 'Felipe', age: 36
}
]
}
]
}
HTML:
<div *ngFor="let personsByCity of peopleByCity">
<div *ngFor="let person of personsByCity.people">
{{ person.name }}
</div>
</div>