21

I've got the following json data returned from a service request:

{
    "entries": [{
        "id": 2081,
        "name": "BM",
        "niceName": "bodmas"
        }]
    }, {
        "id": 8029,
        "name": "Mas",
        "niceName": "Masm"
        }]
    }],
    "count": 2
}

And I am trying the following code in html to loop through this data:

<option ng-repeat="entry in entries" value="{{entry.name}}">{{entry.name}}</option>

I get the following error when I run the code:

Error: [ngRepeat:dupes] Duplicates in a repeater are not allowed. Use 'track by' expression to specify unique keys. Repeater: entry in entries, Duplicate key: string:c

Following is the code for my controller:

myApp.controller("MyController", ['$scope', '$http', '$log', function($scope, $http, $log){
       ...

       $http.get('https://myServiceURL').success(function(data){
                    $scope.entries = data;
        });
}]);

Could somebody help me understand why am I getting that error?

SoEzPz
  • 14,958
  • 8
  • 61
  • 64
skip
  • 12,193
  • 32
  • 113
  • 153
  • 1
    Shouldn't you do `$scope.entries = data.entries;` in your controller ? – Goodzilla Jul 27 '14 at 01:38
  • 2
    Your error message doesn't seem to belong to that snippet of code you provided. It mentions ``make in makes`` when your example says ``entry in entries``. Do you have a list of strings called ``makes`` in scope? If so, *that* is the thing that needs the ``track by`` added. – Martin Atkins Jul 27 '14 at 01:52
  • possible duplicate of [Angular ng-repeat Error "Duplicates in a repeater are not allowed."](http://stackoverflow.com/questions/16296670/angular-ng-repeat-error-duplicates-in-a-repeater-are-not-allowed) – Martin Atkins Jul 27 '14 at 01:56
  • @MartinAtkins: I tried to simplify things using entry for `make` and `entries` for `makes`, mistakenly forgot to modify the error message. Sorry. – skip Jul 27 '14 at 01:58
  • @Goodzilla: Loggin data.entries is giving me `undefined` in result. – skip Jul 27 '14 at 01:59
  • @MartinAtkins: I tried using `entry in entries tracy by $index` like `` and it works alright, but I get the indexes printed for the `option` element and its `value`, like `1,2,3...`. I don't think there would be a duplicate entry in the returned JSON data. Do you think the returned data could be used the way I am using. Is its format correct to be used to loop through like an array? – skip Jul 27 '14 at 02:07
  • @MartinAtkins: How to print the `name` of the entries in the `option` element and its value? – skip Jul 27 '14 at 02:12
  • 1
    Did you try *only* adding ``track by $index`` and not changing your other expressions to ``$index``? e.g ```` – Martin Atkins Jul 27 '14 at 02:16

6 Answers6

38

Add track by $index to your ng repeat so instead of:

<option ng-repeat="entry in entries" value="{{entry.name}}">{{entry.name}}</option>

Try:

<option ng-repeat="entry in entries track by $index" value="{{entry.name}}">{{entry.name}}</option>

There's further information about this in the documentation for this error message:

Occurs if there are duplicate keys in an ngRepeat expression. Duplicate keys are banned because AngularJS uses keys to associate DOM nodes with items.

By default, collections are keyed by reference which is desirable for most common models but can be problematic for primitive types that are interned (share references).

Malcr001
  • 8,179
  • 9
  • 44
  • 57
  • 1
    This looks like a plausible answer but it could be improved with an explanation of why that is necessary e.g. what "tracking" is all about and what ng-repeat does by default that's messing things up. – Martin Atkins Jul 27 '14 at 01:47
  • @martin I've added a link to the angular js docs which explains the issue. – Malcr001 Jul 27 '14 at 01:48
  • @Malcr001: I tried it, but `value="{{entry.name}}"` and `{{entry.name}}` game me empty strings. There is no value inside them. – skip Jul 27 '14 at 02:24
  • @Malcr001: track by was not the solution here because I din't have any duplicate entries in the JSON data. The mistake I was doing when I was calling the service was that I was wrapping the JSON response in a callback function which while looping through the `$scope.entries` was causing the issue and giving me `undefined` for `console.log(data.entries);`. Thank you:) – skip Jul 27 '14 at 03:28
  • @skip ok I was just going by the error message reported in the console. – Malcr001 Jul 27 '14 at 16:10
11

Your JSON is invalid and should be :

{
    "entries": [{
        "id": 2081,
        "name": "BM",
        "niceName": "bodmas"
    }, {
        "id": 8029,
        "name": "Mas",
        "niceName": "Masm"
    }],
    "count": 2
}

Also, make sure you are accessing the document at the right level, use :

$http.get('https://myServiceURL').success(function(data){
    $scope.entries = data.entries;
});

Then, it should work. See this JSBin.

Goodzilla
  • 1,483
  • 11
  • 17
  • You were spot on saying with `$scope.entries = data.entries;` in your comment. The mistake I was doing when I was calling the service was that I was wrapping the JSON response in a callback function which while looping through the `$scope.entries` was causing the issue and giving me `undefined` for `console.log(data.entries);`. Thank you:) – skip Jul 27 '14 at 03:20
  • Along the same lines I had a PHP response with json_encoded data; in the javascript I forgot to JSON.parse() the object,... it worked fine for a while until the return data got more complex. – Quinnland23 May 30 '16 at 20:38
7

If I may add an additional reason as to why this can occur...

If you are doing this with a JS object [] or {}

and you are passing it in to a directive like this

<my-directive my-attribute="{{ myObject }}"></my-directive>

Inside the directive you must turn myObject back into an object by doing this

...
controller: function( $scope ){

  $scope.links = $scope.$eval( $scope.myObject );
....

Then the HTML and ng-repeat will work

...
<span class="" ng-repeat="link in links">
...

ngRepeat does not know how to repeat over a single string.

Here is what the object would look like before $scope.$eval

"[{ hello: 'you' }]"

and after $scope.$eval()

[{ hello: 'you' }] an actual object to repeat over.

The error kind of makes a reference that it cannot repeat of a string. Here is the error that I got.

Error: [ngRepeat:dupes] http://errors.angularjs.org/1.3.0-beta.8/ngRepeat/dupes?p0=link%20in%20links&p1=string%3Al

SoEzPz
  • 14,958
  • 8
  • 61
  • 64
1

It looks like you have a problem with the structure of the data in your scope. Your example JSON shows an object with an entries property and a count property. You then put that whole object in your scope as entries. This means you'd need to access the entries as entries.entries, with the count in entries.count. Perhaps this controller is closer to what you wanted:

myApp.controller("MyController", ['$scope', '$http', '$log', function($scope, $http, $log){
    ...

    $http.get('https://myServiceURL').success(function(data){
        $scope.entries = data.entries;
        $scope.count = data.count;
    });
}]);
Martin Atkins
  • 62,420
  • 8
  • 120
  • 138
  • 1
    Looking closer this doesn't really explain the error as given, since it would be trying to iterate over the strings "entries" and "count"; I think there may be something else going on here that isn't evident in the examples given, since you should only need ``track by`` if your array is of a primitive type like ``string``. – Martin Atkins Jul 27 '14 at 02:09
  • 1
    It's also what I was proposing, but I realised it's not really linked to his error. It just fixes the example, as is. – Goodzilla Jul 27 '14 at 02:11
  • @MartinAtkins and @Goodzilla: Yes, logging `data.entries` and `data.count` are both giving me `undefined` as the result. – skip Jul 27 '14 at 02:20
  • @MartinAtkins: Yes, this was correct. The mistake I was doing when I was calling the service was that I was wrapping the JSON response in a callback function which while looping through the `$scope.entries` was causing the issue and giving me `undefined` for `console.log(data.entries);`. I could accept only one answer, and because Goodzilla figured it out first I have accepted his answer. Please don't mind that. You were equally helpful. Thank you :) – skip Jul 27 '14 at 03:24
-1

duplicates in In ng-repeat is not allowed . 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>
Tunaki
  • 132,869
  • 46
  • 340
  • 423
Yogesh Sharma
  • 197
  • 1
  • 1
  • Could you please relate your answer - now a year ago after the discussion did place originally - to the other, accepted answer. Why it adds value and if maybe "the world changed" and probably the accepted answer does not work any more or something along these lines. Thanks. – Dilettant Jun 10 '16 at 05:37
-1

// To allow this Web Service to be called from script, using ASP.NET AJAX,uncomment the following line.

[System.Web.Script.Services.ScriptService] ==> Uncomment this line If you use .NET Service