Search code examples
angularjsng-optionsangularjs-1.5

AngularJS 1.5 ngOptions Comparison for Displaying a Plain Array


Can anyone tell me why the model option in the first example is selected, and the second one does not for a plain array:

// Plain old array
vm.owner = ['example', 'example2', 'example3', ...];

Where the model vm.model.address.owner = 2;

// EXAMPLE 1 - Works
// Force index to be a number: using id*1 instead of it being a string:
// and the option ('example3') is selected based on the model value of 2 
// indicating the index
<select id="owner"
        name="owner"
        placeholder="Residential Status"
        ng-model="vm.model.address.owner"
        ng-required="true"
        ng-options="id*1 as owner for (id, owner) in vm.owner">
    <option value="">Select Value</option>
</select>

Attempting to not use a hack and using track by instead index 2 is not selected even though the value is still set in the model.

// EXAMPLE 2 - Doesn't Work
// Markup doesn't show value to be a string: using track by, but the 
// option set in the model doesn't cause the option to be selected it 
// remains as the default with a value of ''
<select id="owner"
        name="owner"
        placeholder="Residential Status"
        ng-model="vm.model.address.owner"
        ng-required="true"
        ng-options="owner for (id, owner) in vm.owner track by id">
    <option value="">Select Value</option>
</select>

I find ngOptions to be super confusing so any explanation or solution for example 2 since it is cleaner and not a hack would be great.


Solution

  • Didn't find a solution using track by, but the AngularJS docs for Select had a solution using a parser and formatter so I could get away from using the hack in the question. I tweaked it a bit so if the key was a string it will leave it alone, otherwise it converts it, and this seems to work. Any criticisms or issues that I don't see please comment, otherwise hope this helps someone.

    (function () {
    
        'use strict';
    
        /**
         * Binds a select field to a non-string value via ngModel parsing and formatting,
         * which simply uses these pipelines to convert the string value.
         * @constructor
         * @ngInject
         * ---
         * NOTE: In general matches between a model and an option is evaluated by strict
         * comparison of the model value against the value of the available options.
         * Setting the option value with the option's "value" attribute the value
         * will always be a "string", which means that the model value must also
         * be a string, otherwise the select directive cannot match them
         * reliably.
         */
        function selectConvertKey(_) {
    
            return {
                require: 'ngModel',
                link: function ($scope, $element, $attrs, $ctrl) {
    
                    var ngModelCtrl = $ctrl;
    
                    // Do nothing if no ng-model
                    if (!ngModelCtrl) {
                        return;
                    }
    
                    // ---
                    // PRIVATE METHODS.
                    // ---
    
                    /**
                     * Convert the key to a number if the key is a number.
                     * @param key
                     * @returns {Number}
                     * ---
                     * NOTE: Using Number() instead of parseInt() means that a string
                     * composed of letters and numbers, and start with a number will
                     * not be converted.
                     */
                    function selectConvertKeyParser(key) {
    
                        var keyAsNumber = Number(key);
    
                        // Check if the key is not a number
                        if(_.isNaN(keyAsNumber)) {
                            return key;
                        }
    
                        return keyAsNumber;
                    }
    
                    /**
                     * Convert the key to a string.
                     * @param key
                     * @returns {string}
                     */
                    function selectConvertKeyFormatter(key) {
                        return '' + key;
                    }
    
                    // ---
                    // MODEL PROPERTIES.
                    // ---
    
                    /**
                     * Formatters used to control how the model changes are formatted
                     * in the view, also known as model-to-view conversion.
                     * ---
                     * NOTE: Formatters are not invoked when the model is changed
                     * in the view. They are only triggered if the model changes
                     * in code. So you could type forever into the input, and
                     * the formatter would never be invoked.
                     */
                    ngModelCtrl.$formatters.push(selectConvertKeyFormatter);
    
                    /**
                     * Parsers used to control how the view changes read from the
                     * DOM are sanitized/formatted prior to saving them to the
                     * model, and updating the view if required.
                     */
                    ngModelCtrl.$parsers.push(selectConvertKeyParser);
                }
            };
        }
    
        selectConvertKey.$inject = [
            '_'
        ];
    
        angular
            .module('app')
            .directive('selectConvertKey', selectConvertKey);
    
    })();