15

Here's some quick background info. I just upgraded to Angular 1.4. I'm using an API written in C# for my server-side calls.

A section of my page shows 2 select lists (Project & Sub-project). Both are supposed to default to "(Select a ______)", which I list as the first option of each select with "value" of 0. The appropriate ng-model variables are initialized to 0.

The actual HTML code for the select lists is being generated on the server side using string concatenation, passed to the client via $http, and inserted using a directive that calls $compile (not ideal at all, but my client has pretty much chained me to this API). Prior to the 1.4 update, everything was working nicely.

Now, my Project select list is defaulting to nothing. When I inspect the element, this is what I see...

<select ng-change="updateSubProjList()" ng-model="curProjID">
    <option value="? number:0 ?"></option>
    <option value="0">(Select a Project)</option>
    <option value="1">First Project</option>
    <option value="2">Second Project</option>
    ...
</select>

...with that first "? number:0 ?" entry being the one that is currently selected. My Sub-project select list still initializes just fine, which makes this even more odd.

I know that there were some updates to $compile in the update to AngularJS 1.4, but I can't figure out a solution to my problem. Any assistance would be greatly appreciated.

ThisLanham
  • 745
  • 3
  • 8
  • 20

4 Answers4

24

There seems to be a change in 1.4 related to how the selected option is matched against ngModel by comparing the value attribute in <option value="0"> - it now requires to explicitly use a string to match, rather than allowing for an integer.

In fact, the documentation clearly states:

The value of a select directive used without ngOptions is always a string. When the model needs to be bound to a non-string value, you must either explicitly convert it using a directive ... or use ngOptions to specify the set of options.

To fix, change the initialized value of $scope.curProjID to be a string:


$scope.curProjID = "0"; // instead of $scope.curProjID = 0;

When there is no match (and there isn't, unless you assign a string "0"), select adds an "unknown" option: <option value="? number:0 ?"></option>.

New Dev
  • 48,427
  • 12
  • 87
  • 129
  • This is absolutely right. Thank you! For anyone wondering, my 2nd select statement worked because the options came from and array of objects loaded using "ng-options", as opposed to a giant concatenated string. – ThisLanham Jun 02 '15 at 20:53
  • 1
    As a quick fix I just changed my ng-model binding to include the "toString()" – Jeremy Sep 23 '15 at 01:01
  • @JeremyChild, that is rather a "quick break"... it might work for matching, but it would not update the model (`$scope.reason`) when the selection changes, which is the whole purpose of `ng-model` – New Dev Sep 23 '15 at 03:50
  • Well done, Angular! My face after reading this answer was like :O. – Envil Sep 21 '16 at 12:30
5

There is another way: using a directive implementing a parser and a formatter. See http://plnkr.co/edit/JZPay8jgm5AXKGXDCjSb

thankx goes to narretz!

Pseudo code:

<select convert-number ng-model="x">
   <option value="100">100</option>
   <option value="200">200</option>
</select>


app.directive('convertNumber', function() {
  return {
    require: 'ngModel',
    link: function(scope, el, attr, ctrl) {
      ctrl.$parsers.push(function(value) {
        return parseInt(value, 10);
      });

      ctrl.$formatters.push(function(value) {
        return value.toString();
      });      
    }
  }
});
iwhp
  • 843
  • 6
  • 16
2

I had the same issue. Instead of making sure that my JavaScript code uses string all over the place, I did the following:

Removed the <option ...> items from the HTML file

Introduced a list into JavaScript, similar to:

var orderQuantities = [
        { id: 100, text: '100' },
        { id: 200, text: '200' },
        { id: 300, text: '300' },
];

Did make sure that orderQuantities is visible in the scope

Used ng-option in the select tag as follow:

<select ng-model="vm.entity.OrderQuantity" ng-options="orderQuantity.id as orderQuantity.text for orderQuantity in vm.orderQuantities"></select>
iwhp
  • 843
  • 6
  • 16
  • With this approach, what get bound inside `vm.entity.OrderQuantity` ? Was it only the id (that is, 100, 200, 300) or was it entire selected object (e.g. `{ id: 200, text: '200' }`) ? – JustAMartin Aug 17 '15 at 19:48
0
<select ng-model="x">
   <option value="100">100</option>
   <option value="200">200</option>
</select>

scope.x = '200'

or read this doc. https://docs.angularjs.org/guide/migration

Hakan
  • 1,199
  • 1
  • 14
  • 18