Angularjs ng-options using number for model does not select initial value

AngularjsNg OptionsAngularjs Ng-Options

Angularjs Problem Overview


I'm trying to use ng-options with a <select> to bind a numeric integer value to a list of corresponding options. In my controller, I have something like this:

myApp.controller('MyCtrl', function() {
    var self = this;
    
    var unitOptionsFromServer = {
        2: "mA",
        3: "A",
        4: "mV",
        5: "V",
        6: "W",
        7: "kW"
    };
    
    self.unitsOptions = Object.keys(unitOptionsFromServer).map(function (key) {
        return { id: key, text: unitOptionsFromServer[key] };
    });
    
    self.selectedUnitOrdinal = 4; // Assume this value came from the server.
});

HTML:

<div ng-controller="MyCtrl as ctrl">
    <div>selectedUnitOrdinal: {{ctrl.selectedUnitOrdinal}}</div>
    <select ng-model="ctrl.selectedUnitOrdinal" ng-options="unit.id as unit.text for unit in ctrl.unitsOptions"></select>
</div>

And here's a jsFiddle demonstrating the problem, and some other approaches I've taken but am not happy with.

The select option is being initialized with an empty value, instead of "mV" as expected in this example. The binding seems to work fine if you select a different option -- selectedUnitOrdinal updates properly.

I've noticed that if you set the initial model value to a string instead of a number, then the initial selection works (see #3 in the fiddle).

I really would like ng-options to play nice with numeric option values. How can I achieve this elegantly?

Angularjs Solutions


Solution 1 - Angularjs

Angular's documentation for the ng-select directive explains how to solve this problem. See https://code.angularjs.org/1.4.7/docs/api/ng/directive/select (last section).

You can create a convert-to-number directive and apply it to your select tag:

JS:

module.directive('convertToNumber', function() {
  return {
    require: 'ngModel',
    link: function(scope, element, attrs, ngModel) {
      ngModel.$parsers.push(function(val) {
        return val != null ? parseInt(val, 10) : null;
      });
      ngModel.$formatters.push(function(val) {
        return val != null ? '' + val : null;
      });
    }
  };
});

HTML:

<select ng-model="model.id" convert-to-number>
  <option value="0">Zero</option>
  <option value="1">One</option>
  <option value="2">Two</option>
</select>

Note: I found the directive from the doc does not handle nulls so I had to tweak it a little.

Solution 2 - Angularjs

Maybe it's a bit messy, but result can be achieved without special functions right in ng-options

<select ng-model="ctrl.selectedUnitOrdinal" ng-options="+(unit.id) as unit.text for unit in ctrl.unitsOptions"></select>

Solution 3 - Angularjs

It's because when you get the unit.id it's returning a string not an integer. Objects in javascript store their keys as strings. So the only way to do it is your approach of surrounding 4 by quotations.

Edit

<select ng-model="ctrl.selectedUnitOrdinal" ng-options="convertToInt(unit.id) as unit.text for unit in ctrl.unitsOptions"></select>

$scope.convertToInt = function(id){
    return parseInt(id, 10);
};

Solution 4 - Angularjs

You can also define filter number, this filter will automatically parse string value to int:

<select ng-model="ctrl.selectedUnitOrdinal" ng-options="unit.id|number as unit.text for unit in ctrl.unitsOptions"></select>


angular.module('filters').filter('number', [function() {
        return function(input) {
            return parseInt(input, 10);
        };
    }]);

Solution 5 - Angularjs

Just to add Christophe's answear: easiest way to achive and maintaint it is to make a directive:

JS:

.directive('convertToNumber', function() {
  return {
    require: 'ngModel',
    link: function(scope, element, attrs, ngModel) {
      ngModel.$parsers.push(function(val) {
        //saves integer to model null as null
        return val == null ? null : parseInt(val, 10);
      });
      ngModel.$formatters.push(function(val) {
        //return string for formatter and null as null
        return val == null ? null : '' + val ;
      });
    }
  };
});

Christophe's answear wont work correctly for '0' as it returns false on "val?" test.

Solution 6 - Angularjs

You can use ng-value attribute. Like this:

<select ng-model="model.id">
  <option ng-value="0">Zero</option>
  <option ng-value="1">One</option>
  <option ng-value="2">Two</option>
</select>

Solution 7 - Angularjs

I think the other solutions are overly complex for simply establishing the value as an integer. I would use:

<select ng-model="model.id">
    <option ng-value="{{ 0 }}">Zero</option>
    <option ng-value="{{ 1 }}">One</option>
    <option ng-value="{{ 2 }}">Two</option>
</select>

Solution 8 - Angularjs

Angular is seeing your id property as a string, add quotes:

self.selectedUnitOrdinal = "4";

Solution 9 - Angularjs

Very similarly to tymeJV's answer, simple workaround is to convert the default selection number to a string like this:

self.selectedUnitOrdinal = valueThatCameFromServer.toString();

This way, you don't have to hardcode the number, you can just convert any received value. It's an easy fix without any filters or directives.

Solution 10 - Angularjs

<select ng-model="ctrl.selectedUnitOrdinal" ng-options="+(unit.id) as unit.text for unit in ctrl.unitsOptions"></select>

Solution 11 - Angularjs

Make id property in unitsOptions as number

self.unitsOptions = Object.keys(unitOptionsFromServer).map(function (key) {
        return { id: +key, text: unitOptionsFromServer[key] };
    });

http://jsfiddle.net/htwc10nx/

Solution 12 - Angularjs

While the solution provided by Mathew Berg works it will probably break other selects that use sting-valued select options. This may be a problem if you try to write one universal directive for handling selects (like I did).

A nice workaround is to check if the value is a number (even if it is a string containing a number) and only than do the conversion, like so:

angular.module('<module name>').filter('intToString', [function() {
        return function(key) {
            return (!isNaN(key)) ? parseInt(key) : key;
        };
    }]);

or as a controller method:

$scope.intToString = function(key) {
    return (!isNaN(key)) ? parseInt(key) : key;
};

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionBrandonLWhiteView Question on Stackoverflow
Solution 1 - AngularjsChristophe LView Answer on Stackoverflow
Solution 2 - Angularjsre-gorView Answer on Stackoverflow
Solution 3 - AngularjsMathew BergView Answer on Stackoverflow
Solution 4 - AngularjszooblinView Answer on Stackoverflow
Solution 5 - Angularjss3r3kView Answer on Stackoverflow
Solution 6 - AngularjsVid StoschitzkyView Answer on Stackoverflow
Solution 7 - AngularjsTed SchecklerView Answer on Stackoverflow
Solution 8 - AngularjstymeJVView Answer on Stackoverflow
Solution 9 - Angularjsemil.cView Answer on Stackoverflow
Solution 10 - AngularjsmangeshView Answer on Stackoverflow
Solution 11 - AngularjsoadcubView Answer on Stackoverflow
Solution 12 - AngularjsbwitkowiczView Answer on Stackoverflow