I have an array that I display using ngRepeat and I have a form at the bottom that enables adding to the said array. This is used to work in previous version of angular; The way items are tracked in ngRepeat have changed in newer versions of angular, so I'm not sure yet if this is a big, but when I push an item to an array, all the items seem to be binded to the same model.
This is how I add an item:
var vm = this;
this.items = [];
this.addItem = function(item) {
vm.items.push(item);
}
and I loop over them like this:
<ul>
<li ng-repeat="item in main.items track by $index">{{item.id}} - {{item.value}}</li>
</ul>
<hr>
<input ng-model="main.newItem.id" type="text" placeholder="id">
<input ng-model="main.newItem.value" type="text" placeholder="value">
<button ng-click="main.addItem(main.newItem)">Add Item</button>
Here is a plnkr demonstrating the issue.
Answer by user Blackhole
Need to make a copy of the item before adding it to the array
vm.items.push(angular.copy(item));
Related
I have a problem when adding several sections and within each section attempt to add other questions, but these are added in all sections. I hope someone can help.
code here: http://goo.gl/XQas0x
Just one questions array is not enough to represent each items section.
If you think about it, there must be one array per item. You could make questions a property of each item as NMittal suggests. That will work.
Another approach, if you want to keep those separated, is to ensure the structure of your questions array matches the items array.
http://plnkr.co/edit/s7RUfUFcjSnr3L0GajkX?p=preview
We're using a nested ng-repeat, so we must temporarily store the $index of the items array for use in the ng-repeat for questions.
<div ng-repeat="item in items" data-sec="#{{$index}}" ng-init="itemIndex=$index">
next use that variable:
<div class="input-group" ng-repeat="question in questions[itemIndex]" style="margin-bottom: 15px;">
Now the ng-repeats are at least looking in the right place, but it's not enough because Add_Question() and Remove_Question() don't know about this new data structure. Here's how to update them:
<button ng-click="Add_Question(itemIndex)" class="btn btn-success">Agregar Pregunta</button>
and
<span class="input-group-addon" style="cursor: pointer;" ng-click="Remove_Question($index, itemIndex)">
and finally the JS side:
$scope.questions = {};
$scope.Add_Question = function(itemIndex) {
if($scope.questions[itemIndex] === undefined){
$scope.questions[itemIndex] = [];
}
$scope.questions[itemIndex].push({
});
};
$scope.Remove_Question = function(index, itemIndex) {
$scope.questions[itemIndex].splice(index, 1);
}
This is because your questions array is in the controller scope, and any changes to the collection will reflect to all the child scopes. One option is to keep the questions collection as part of your items object, and make updates to each individual questions collection on add/remove,
$scope.Add_Section = function() {
$scope.items.push({questions:[]});
};
And
<div class="input-group" ng-repeat="question in item.questions" style="margin-bottom: 15px;">
Now since questions become a nested collection, removing questions become a little tricky, you need both items' and questions' index to remove. $parent comes in handy here to get the items' index
$scope.Remove_Question = function(parentIndex,index) {
$scope.items[parentIndex].questions.splice(index, 1);
}
and
<span class="input-group-addon" style="cursor: pointer;" ng-click="Remove_Question($parent.$index,$index)">
Updated Plunk
I am new to AngularJS and i dont know is it possible to delete all scope variables of a controller.I am using ng-controller with ng-repeat, like this.
<div ng-controller="main">
<div ng-repeat="x in list" ng-controller="test">
<input type="text" ng-model="text">
<span ng-click="remove($index)"> x </span>
<div>
</div>
JS
myapp.controller('main',function($scope){
$scope.list=[1,2,3,4]
})
myapp.controller('test',function($scope){
$scope.text="untitiled"
})
I want to remove the clicked scope.Can anyone help me or please suggest me a better way. Thanks
The question isn't very clear, but it looks like you may want to remove the item after clicking. Since you are passing into the remove function the index, you can splice it out. The DOM will autoupdate and remove that from the list:
$scope.remove = function(i) {
$scope.list.splice(i,1);
console.log($scope.list);
}
In the event you are doing something different in that you only want to hide it, you would push the index onto another array and then use something like ng-show or ng-hide.
$scope.remove2 = function(i) {
$scope.hideList.push(i);
}
$scope.shouldHide = function(i) {
return $scope.hideList.indexOf(i)!=-1;
}
<div ng-repeat="number in list2" >
{{number}}
<span ng-hide='shouldHide($index)' ng-click="remove2($index)"> x </span>
</div>
Here is a simple example of both scenarios. In real life, usually we are dealing with arrays of objects and what you might be doing is setting a property on one of the objects to hidden and controlling it that way.
Demo: http://plnkr.co/edit/G7UINKUCBJ4yZhQNtuJ2?p=info
If you actually want to remove all the keys from the scope:
function removeKeys() {
for(key in $scope) {
if (key.substr(0,1)!='$' && key!='this')
delete $scope[key];
}
}
I have an app that creates input elements dynamically when a user clicks a button.
It looks something like this:
[ input ] [+]
That input has an ng-model and I can use $compile when appending new inputs to have Angular treat them as if they were in the DOM at bootstrap time.
I can increment the ng-model value to say "item1", "item2" etc, but I don't like that.
I would like to know if there's a away to have an array that holds all values from all inputs.
Thanks.
You could try this:
HTML
<div ng-controller="myCtrl">
<button ng-click="addInputField()">add</button>
<ul>
<li ng-repeat="item in items">
<input type="text" id="item{{input.id}}" ng-model="item[$index]"/>
</li>
</ul>
data:
<div ng-repeat="item in items">
{{item}}
</div>
</div>
CONTROLLER
angular.module('app',[])
.controller('myCtrl',function($scope){
$scope.items = [];
$scope.addInputField = function(){
$scope.items.push({});
}
});
Here is the JSFiddle demo
'Items' is the data that binding to the input field list. You can maintain the view by updating the items array within scope and bind the elements of array to each input field view.
Hope this is helpful.
I'm trying to create a form like below, this using ng-repeat directive in angular and it whenever I created a new row complains
"Duplicates in a repeater are not allowed.".
While I understand the solution for this is by putting "track by $index", however it causes another issue, which clicking delete on one row deletes the value of other field. So I suspect that track by index is OK for static text but not input form. So how to use ng-repeat correctly for my case?
My jsfiddle : demo.
Edit : I do aware that json array of object will solve my issue ( because for object angular create $$hashKey ) and already implemented this for most of my other module. But I am actually expecting some fix that can be done without really change my json array of string. Sorry for not being clear.
My current code :
HTML
<div class="row-fluid spacer10">
<a ng-click="addAKA()" class="btn btn-primary spacer5 left30"><i class="icon-plus icon-white"></i> Add New Alias</a>
</div>
<div class="row-fluid spacer10"></div>
<div class="row-fluid spacer5" ng-repeat="item in aliasList track by $index">
<input type="text" class="span6 left30" ng-model="item">
<button class="btn btn-danger" ng-click="deleteAKA($index)">delete</button>
<BR/>
</div>
Javascript
$scope.addAKA = function ()
{
if($scope.aliasList == null)
{
$scope.aliasList = [];
}
$scope.aliasList.push("");
$scope.aliasjson = JSON.stringify($scope.aliasList);
}
$scope.deleteAKA = function (idx)
{
var aka_to_delete = $scope.aliasList[idx];
$scope.aliasList.splice(idx, 1);
$scope.aliasjson = JSON.stringify($scope.aliasList);
}
I would guess this is caused when there are more than one empty strings in the list.
If this is the case, it is caused because any two empty strings are equals in JS and Angular repeater does not allow duplicate values (as clearly stated in the message). This is a valid decision as they have to relate an object in the list with its DOM tree to minimize DOM manipulation.
A solution would be to insert simple objects containing the string in the model:
$scope.addAKA = function () {
...
$scope.aliasList.push({value:""});
...
};
And adjust your template:
<input type="text" class="span6 left30" ng-model="item.value">
Since all new objects are different, your problem should be solved.
See a fiddle where a filter is implemented to transform the model back to a list of strings.
When you type in a new created input, your list stays the same. Angular on any list change will update the view (ng-repeat) and remove all new stored text. Therefore we need to add ng-change to update our list on any input change
Add ng-change="change(i, $index) to your item and it should work
HTML
<div ng-controller='ctrl'>
<ol>
<li ng-repeat='i in list track by $index'>
<input type='text' ng-model='i' ng-change="change(i, $index)"></input>
<button ng-click='deleteItem($index)'>Delete</button>
</li>
</ol>
<button ng-click='addItem()'>Add</button>
<div>ITEM: {{list | json}}</div>
</div>
Javascript
angular.module("app", []).controller("ctrl", function ($scope) {
$scope.list = ["one","two"];
$scope.addItem = function ()
{
$scope.list.push("");
};
$scope.deleteItem = function (idx)
{
var item_to_delete = $scope.list[idx];
$scope.list.splice(idx, 1);
};
$scope.change = function (item, idx)
{
$scope.list[idx] = item;
};
});
See fixed Demo in DEMO
Yes, pushing more than one empty string will result in ng-repeat complaining.
In addition, you can also try:
if ($scope.aliasList.indexOf(VALUE_TO_ADD) === -1) {
...
}
I'm trying to create a form like below, this using ng-repeat directive in angular and it whenever I created a new row complains
"Duplicates in a repeater are not allowed.".
While I understand the solution for this is by putting "track by $index", however it causes another issue, which clicking delete on one row deletes the value of other field. So I suspect that track by index is OK for static text but not input form. So how to use ng-repeat correctly for my case? See my JSFiddle for demo.
My current code :
HTML
<div class="row-fluid spacer10">
<a ng-click="addAKA()" class="btn btn-primary spacer5 left30"><i class="icon-plus icon-white"></i> Add New Alias</a>
</div>
<div class="row-fluid spacer10"></div>
<div class="row-fluid spacer5" ng-repeat="item in aliasList track by $index">
<input type="text" class="span6 left30" ng-model="item">
<button class="btn btn-danger" ng-click="deleteAKA($index)">delete</button>
<BR/>
</div>
Javascript
$scope.addAKA = function ()
{
if($scope.aliasList == null)
{
$scope.aliasList = [];
}
$scope.aliasList.push("");
$scope.aliasjson = JSON.stringify($scope.aliasList);
}
$scope.deleteAKA = function (idx)
{
var aka_to_delete = $scope.aliasList[idx];
$scope.aliasList.splice(idx, 1);
$scope.aliasjson = JSON.stringify($scope.aliasList);
}
Just change the way you iterate. Instead of this:
<div ng-repeat="item in aliasList track by $index">
do this:
<div ng-repeat="item in aliasList">
$index will be still available inside the element, use like this:
<button ng-click='deleteItem($index)'>Delete</button>
See this JSFiddle for a correct solution
There are multiple problems with your approach.
Since you are directly binding a string to ng-model and ng-repeat creates a child scope, any change to the value would not reflect back. Change you scope model to something like
$scope.list = [{text:"one"},{text:"two"}];
and bind to i.text instead of binding to i as you did earlier.
The deleteItem method was called using item instead of index. See my fiddle here
http://jsfiddle.net/JS6aJ/1/