angular js: ng-repeat no longer allowing duplicates

Just ran into an interesting issue with angularjs combined with local storage.

Some facts:

1. angularjs adds a $$hashKey property to objects in collections that are being used with ng-repeat and don’t otherwise have an identifier configured.

2. the $$hashKey is typically generated by incrementing a shared variable. This is a fairly simple way to reliably generate new ids.

In our app, we are using local storage to continually save changes that the user has made on the page . This allows them to always have a working draft that they can discard at any time. If the browser dies or they go away for a while, they never lose their work.  They always have the option to submit or discard available to them.

Now, if you store an object in local storage, you’re typically storing the whole object unchanged. We were, of course, which meant that we were including the $$hashKey property on all of our objects kept in local storage.

So, when you reload the page, we would load the draft data from local storage, including the $$hashKeys.

For items added that didn’t have a $$hashKey set yet, angular would generate one. Only, because it’s an incrementing value, it would be behind some of those already existing in other tracked objects. Collisions! Duplicate IDs!

This totally just happened:

Error: Duplicates in a repeater are not allowed …

Oh NO!

In angular 1.1.4, it now throws an error when there are duplicate objects in an ng-repeat, where this was previously tolerated.

There are a couple of ways to get around this.

In our case, we only ran into this issue because we were storing our objects in local storage and then retrieving them again later on new page loads.
We were storing our objects as JSON, and so we were using JSON.stringify and JSON.parse when we get from local storage.  So it was easy to extend our stringify method to strip out the hash properties by passing in a replacer, like so:

JSON.stringify(value, function (key, val) {
     if (key == '$$hashKey') {
         return undefined;
     }
     return val;
});

When we load objects and feed them to an ng-repeat, angular will add hash keys as it needs, so this works fine for us.

But… there is an easier way if you expect duplicates to be possible in your collections. It’s just hard to find. Sometimes looking at the source really is the best documentation…

Anyway,  one way you can handle this is to tell angular that you’d rather have the ng-repeat treat the items’ indexes as the id rather than expecting an id (the default, which like in our case results in generated hash keys when there was no id).

This can be done by defining the ng-repeat using the following style:

<ul>
    <li ng-repeat="thing in things track by $id($index)">
    .....
    </li>
</ul>

Whammy.

Lesson learned, sometimes you’re not just storing what you think you’re storing. You might also be storing framework state information.  And that state may not always be valid.

About these ads

8 thoughts on “angular js: ng-repeat no longer allowing duplicates

  1. Mark, can you do a presentation about AngularJS at UofA? I’m running a small study group with CS students to learn some new technologies,

    Thanks.

  2. …and if you are using any ” | filter: …” in the ng-repeat, then put the “track by $index” AFTER the filter declaration.

  3. It is my understanding that if, instead of JSON.parse and JSON.stringify, you would use angular.fromJson and angular.toJSON, respectively, this problem would also not have occured.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s