Why does AngularJS fail to initialize Select Option (drop list) when ngmodel is used with nested object?

StackOverflow https://stackoverflow.com/questions/23414671

Question

I have a complex object which contains some nested arrays of objects. Inside one of those inner objects is a value which is the id for an item in another list. The list is just a look-up of Codes & Descriptions and looks like the following:

[
  { "id": 0, "value": "Basic"},
  { "id": 1, "value": "End of Month (EOM)"},
  { "id": 2, "value": "Fixed Date"},
  { "id": 3, "value": "Mixed"},
  { "id": 4, "value": "Extra"}
]

However, I only carry the value in the nested object. The Select Option list (drop list) will display all of the values in the previous list so the user can make his/her selection.

Binding Via ng-model

I then bind the value returned from the Select/Option directly to the nested object. That way, when the user makes a selection my object should be updated so I can just save (post) the entire object back to the server.

Initialization Is The Problem

The selection does work fine and I can see that the values are all updated properly in my nested object when a user selects. However, I couldn't get the UI (select/option) to be initialized to the proper value when I retrieved the (nested) object from the server.

Input Type Text Was Binding Properly My next step was to add an text box to the form, bind it to the same ng-model and see if it got initialized. It did.

This is a large project I was working on so I created a plnkr.co and broke the problem down. You can see my plnkr in action at: http://plnkr.co/edit/vyySAmr6OhCbzNnXiq4a?p=preview

My plunker looks like this:

plunker samples

Not Initialized

I've recreated the exact object from my project in Sample 1 and as you can see the drop list is not selected properly upon initialization since the value(id) is actually 3, but the drop list doesn't show a selected value.

Keep In Mind: They Are Bound And Selecting One Does Update Values

If you try the plunker you will see that the values are bound to the select/option list because even in the samples which do not initialize properly, when you select an item the other bound items are instantly updated.

Got It Working : Hack!

I worked with it a long time and kept created fake objects to see which ones work and which don't. It only works, once I changed the value object to one that looks like the following:

$scope.x = {};
$scope.x.y = 3;

Now, I can bind x.y (ng-model="x.y") to select/option and it initializes the select/option list as you would expect. See Sample 2 in the plunker and you will see that "mixed" (id value 3) is chosen as expected.

Additional One Works

I also learned that the following will work:

$scope.lastObj = {};
$scope.lastObj.inner = [];
$scope.lastObj.inner.push(3);

More Nesting

In that case I can bind lastObj.inner to the select/option list and again you can see in Example 3 that it still works. That is an object which contains an array which contains the value.

Minimal Nesting That Fails

However, Sample 4 in the plunker displays the final amount of nesting which does not work with the AngularJS binding.

$scope.thing = {};
$scope.thing.list=[];
$scope.thing.list.push({"item":"3"});

This is an object which contains an array which contains an object with a value. It fails to bind properly on the select/option but not the text box.

Can Anyone Explain That, Or Is It A Bug, Or Both?

Can anyone explain why the select/option fails to bind / initialize properly in this case?

A Final Note

Also, be strong and do not try to explain why you think the nesting of my objects should be different. That's not under discussion here, unless you can tell me that JavaScript itself does not support that behavior.

However, if you can explain that Angular cannot handle this deep of nesting and why, then that is a perfectly valid answer.

Thanks for any input you have.

Was it helpful?

Solution

You are messed up with primitive types. It means you should insert

$scope.vm.currentDocument.fieldSets[0].fields.push({"value":3});

instead of

$scope.vm.currentDocument.fieldSets[0].fields.push({"value":"3"});

Note the difference of {"value":3} and {"value":"3"}

First one defines an object with property "value" with Integer type, and the second one defines an object with property "value" with String type. As Angular checks type match, it becomes that ("3" === 3) evaluates as false, this is why angular cant find selected option. This is how it supposed to work.

OTHER TIPS

Also note that - as Armen points out - objects are passed by reference as opposed to primitives which are pass-by-value.

Because of this fact, normally initializing a select box via ngModel from JSON (say, from a $resource record) you will need to set the model value to the specific array element/object property that is being internally checked for equality by Angular to the elements in the ngOptions (or repeated options elements with ng-values assigned to the same record objects). No two distinct objects in JS are considered equal, even if they have identical property names/values.

Angular has one way around this: use the "track by" clause in your ngOptions attribute. So long as you have a guaranteed-unique value (such as a record index from a db) Angular will check the value of the property between the model value and the records in ngOptions.

See https://docs.angularjs.org/api/ng/directive/select for more.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top