The right way to use track by with ng-options in Angular 1.4 & 1.5 — Why can’t I select this option?

There is a change in the way AngularJS tracks each option of the select element that was released as part of Angular 1.4.

Instead of using the index of each item in your list when you don’t use track by and using the value pased to track by when you use it, it will use an object hash of the item or track by property.

If you are using track by with ng-options, and using it correctly, this change will not affect you. Otherwise, you may get some interesting side effects. Let’s see what this means.

Let’s assume the following model:

$scope.list = [
    {key: 1, text: "One"},
    {key: {id:2}, text: "Two"},
    {key: {id:3}, text: "Three"}

You notice that the first element has a simple key of number type. The others have different objects as their keys.

What would be the effect of that in Angular 1.4?

Without “track by”

Given the HTML:

<select ng-model="sample"
    ng-options="item.text for item in list">

Angular Will generate:

<select ng-model="sample"
        ng-options="item.text for item in list"
        class="ng-pristine ng-valid ng-touched">
    <option value="?" selected="selected"></option>
    <option value="object:3" label="One">One</option>
    <option value="object:4" label="Two">Two</option>
    <option value="object:5" label="Three">Three</option>

(I have formatted the generated HTML for easy read)

Angular uses the a hash of the object as its key. For items in the list used without track by we get the useless object bit, followed by a number that seems unique, but isn’t the index of the element for example.

Of course we also get the empty element added as we didn’t select an initial value. I talked about this before in detail here.

With “track by”

If we change our HTML to:

<select ng-model="sample" 
    ng-options="item.text for item in list track by item.key">

Where we tell Angular to differentiate between the items in the list by their key property, we get the following HTML generated in runtime:

<select ng-model="sample"
        ng-options="item.text for item in list track by item.key"
        class="ng-pristine ng-valid ng-touched">
    <option value="?" selected="selected"></option>
    <option value="1" label="One">One</option>
    <option value="[object Object]" label="Two">Two</option>
    <option value="[object Object]" label="Three">Three</option>

Ignoring the first item and tabs and form classes again, and only looking at the value attribute of each option tag, we notice the following:

The generated hash code for the simple number value (key: 1), was the value itself, 1.

The generated hash code for the objects, was just what you’d get if you write the object in an old browser console log, a silly useless [object Object].

Why does this matter?

Try to select item “Two” from this example (in Chrome, for example):

You can’t.

Because both item 2 and item 3 have the same value string [object Object].

Here’s the code to play with.

What to learn from this

I’d say, we have two best practices when using select and ng-options with AngularJS:

  • Always use track by
  • Always set your key to a simple value, a string or a number

This will save you too much trouble.

Share With Friends:

How did I learn that?

As a bonus for coming here, I'm giving away a free newsletter for web developers that you can sign up for from here.

It's not an anything-and-everything link list. It's thoughtfully collected picks of articles and tools, that focus on Angular 2+, ASP.NET (4.x/MVC5 and Core), and other fullstack developer goodies.

Take it for a test ride, and you may unsubscribe any time.

You might also want to support me by checking these out [Thanks]: