angular / angular.js

AngularJS - HTML enhanced for web apps!
https://angularjs.org
MIT License
58.81k stars 27.5k forks source link

ng-model not binding <select> list values in 1.4.rc2 #11890

Closed RickStrahl closed 9 years ago

RickStrahl commented 9 years ago

I have a list of static values that are bound to a list and I'm trying to get the list to display the value from the model.

The model value is an integer:

wm.activeResource.ValueType = 0;

Then in the html I'm binding this value to a dropdown list of static values:

{{view.activeResource.ValueType}}  <-- correctly displays 0 -->

<!-- list displays empty element --->
<select ng-model="view.activeResource.ValueType">
    <option value="0">Text</option>
    <option value="2">Markdown</option>
    <option value="1">Binary</option>
</select>

This worked fine in v1.3 but in 1.4 the list comes back with no selection. Or, more specifically it has a selection of an injected element:

<option value="? number:0 ?"></option>
Narretz commented 9 years ago

Is this a correct reproduction? http://plnkr.co/edit/k09Xh9xEgcXQBbKTkRdh So the select does not reflect the model?

petebacondarwin commented 9 years ago

The reason for this is that in 1.4 we test for exact equality in the select directive. If you have static options then the values of these options are strings. See http://plnkr.co/edit/KCM6Kz22sB5GVcPvOZgT?p=preview

You can provide a simple directive to convert the value from number to string and back between the model and view: http://plnkr.co/edit/BnNkVENvNYyArbRjVaov?p=preview

petebacondarwin commented 9 years ago

We should probably highlight this in the docs for select if it is not already.

RickStrahl commented 9 years ago

While I think that this makes sense conceptually and probably for performance as well, I think this has a terrible usability aspect. I would imagine that this is a fairly common scenario that needs to be addressed and there's no intuitive discovery of this behavior until it fails. If you're new and starting out you're not going to make the connection that you have a type conversion problem.

Even the solution of requiring a custom directive to make this work seems heavy handed. Shouldn't there be something built in to provide these types of conversions for you more easily without having to create a component?

I don't want to derail this issue - the solution given is straight forward enough for me to fix and I can live with that. Thanks for pointing to the source of the issue and the solution!

But I think in the grander scheme of things this should be thought over going forward (not sure how this will be handled in 2.0).

Narretz commented 9 years ago

We previously compared the model and attribute value with == which did type conversion. However, did caused false to be equal to 0 for example. Also bad. The root problem is simply that HTML does only allow strings inside value attributes, so your integers become strings in the DOM. You can use ngOptions to remedy this problem.

petebacondarwin commented 9 years ago

A more pernicious issue with using == and implicit conversion is that on the way "out" of such a select your model would change from being a number to a string.

petebacondarwin commented 9 years ago

I updated the select docs to highlight this issue and to provide a running example of using a custom conversion directive.

RickStrahl commented 9 years ago

Thanks all. The docs and sample are definitely a good addition.

annechinn commented 9 years ago

I have a scenario that I am still unable to get to work, even with the directive: I have a set of enumerated values that I want to use for the options. It is unable to bind to get the initial value correctly, but if I put in the value directly for each enumerated value it works correctly.

<select ng-model="vm.frameworkContext.frameworkViewType" convert-to-number>
  <option value="{{vm.enums.FrameworkViewType.STATE_FRAMEWORK_ONLY}}">
    State Framework Only
  </option>
  <option value="{{vm.enums.FrameworkViewType.INSTRUCTION_FRAMEWORK_ONLY}}">
    Instructional Framework Only
  </option>
  <option value="{{vm.enums.FrameworkViewType.STATE_FRAMEWORK_DEFAULT}}">
    State Framework Default
  </option>
  <option value="{{vm.enums.FrameworkViewType.INSTRUCTIONAL_FRAMEWORK_DEFAULT}}">
    Instructional Framework Default
  </option>
</select>

where

vm.enums.FrameworkViewType: {
  UNDEFINED: 0,
  STATE_FRAMEWORK_ONLY: 1,
  STATE_FRAMEWORK_DEFAULT: 2,
  INSTRUCTIONAL_FRAMEWORK_DEFAULT: 3,
  INSTRUCTION_FRAMEWORK_ONLY: 4
}
gkalpak commented 9 years ago

@annechinn, it seems to be working as expected. If you experience any issues, please open a new issue, providing all necessary info to reproduce the problem (preferrably a live demo of the bug, using CodePen, Plnkr, etc).

petebacondarwin commented 9 years ago

@annechinn I think your problem might be that option values are strings, while your enum values are integers. This is precisely the problem discussed here and now described in the select.

annechinn commented 9 years ago

I created the plunker and found that it is reproducible in 1.3, but fixed in 1.4, so I will not create a new issue. In case you're interested, i've provided a link to the plunker to demontrate that it was broken in 1,3, even with the convert-to-number directive.

http://plnkr.co/edit/A5onypmeEm8xUzSDdpG1?p=preview

On Wed, Oct 28, 2015 at 2:38 AM, Pete Bacon Darwin <notifications@github.com

wrote:

@annechinn https://github.com/annechinn I think you problem might be that option values are strings, while your enum values are integers. This is precisely the problem discussed here and now described in the select.

— Reply to this email directly or view it on GitHub https://github.com/angular/angular.js/issues/11890#issuecomment-151780334 .

gkalpak commented 9 years ago

@annechinn, the issue demonstrated in your plnkr is not the same (hence the convertToNumber directive does not solve it). It is a timing problem (due to interpolations in value attributes) and can be reproduced even with string model-values. It can be worked around by initializing your model values in a timeout.

I don't think such kind of bugs are getting fixed on v1.3.x (since it's an old version).

campbeln commented 8 years ago

I understand why === is much more better-er than ==, but this causes all sorts of issues when trying to bind to an object (as I am currently fighting with). We have an object barfing out as { boundValue: 69 } yet with the changes as described above, I'm going to need to do something equivalent to this: { boundValue: 69, boundValueStr: "69" }... really!?

How about due to the new use of === and the fact that the right-hand side of this equation is always a string thanks to the HTML DOM, how about we force the left-hand side to be a string as well? Or at least give us the option to do so without some hack-y secondary directive (which I've still not gotten to work right in our case, by the way).

Narretz commented 8 years ago

@campbeln The value attribute in selects can only be a string, so when you have numbers you should use the ngOptions directive. Why the directive workaround doesn't work in your case is hard to say, but that's a question better asked on Stackoverflow etc. Lastly, if you have an idea how to improve the directive, opening a new issue is probably the better idea.

campbeln commented 8 years ago

@Narretz ng-options doesn't bind it either in our tests!? Besides it seems boneheaded to require all SELECT values to be strings when the right is always a string. Then we devs will be required to coerce all API values to strings if we need to use a SELECT?!

Narretz commented 8 years ago

If ng Options doesn't bind satisfactorily please provide a minimal demo in a plnkr.co or similar and I'll have a look.

campbeln commented 8 years ago

Raised new issue here - https://github.com/angular/angular.js/issues/13955

You can't use ng-options if you're doing something stupid like returning a new array of objects from a function call (e.g. ng-options='item in myFunct()'). Then you try and use track by but that cannot be used with selects (as in the horrible ng-options version, not HTML DOM objects). What _may_ get us around it is to cache the results from the first call so that all references are the same and we won't need track by, but I'm not sure that will work in all cases in our system.