3
votes

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.".

enter image description here

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);
}
3

3 Answers

6
votes

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.

2
votes

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

0
votes

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) {
      ...
  }