selectDirective = function() {

  return {
    restrict: 'E',
    require: ['select', '?ngModel'],
    controller: SelectController,
    priority: 1,
    link: {
      pre: selectPreLink,
      post: selectPostLink
    }
  };

  function selectPreLink(scope, element, attr, ctrls) {

      var selectCtrl = ctrls[0];
      var ngModelCtrl = ctrls[1];

      // if ngModel is not defined, we don't need to do anything but set the registerOption
      // function to noop, so options don't get added internally
      if (!ngModelCtrl) {
        selectCtrl.registerOption = noop;
        return;
      }


      selectCtrl.ngModelCtrl = ngModelCtrl;

      // When the selected item(s) changes we delegate getting the value of the select control
      // to the `readValue` method, which can be changed if the select can have multiple
      // selected values or if the options are being generated by `ngOptions`
      element.on('change', function() {
        selectCtrl.removeUnknownOption();
        scope.$apply(function() {
          ngModelCtrl.$setViewValue(selectCtrl.readValue());
        });
      });

      // If the select allows multiple values then we need to modify how we read and write
      // values from and to the control; also what it means for the value to be empty and
      // we have to add an extra watch since ngModel doesn't work well with arrays - it
      // doesn't trigger rendering if only an item in the array changes.
      if (attr.multiple) {
        selectCtrl.multiple = true;

        // Read value now needs to check each option to see if it is selected
        selectCtrl.readValue = function readMultipleValue() {
          var array = [];
          forEach(element.find('option'), function(option) {
            if (option.selected && !option.disabled) {
              var val = option.value;
              array.push(val in selectCtrl.selectValueMap ? selectCtrl.selectValueMap[val] : val);
            }
          });
          return array;
        };

        // Write value now needs to set the selected property of each matching option
        selectCtrl.writeValue = function writeMultipleValue(value) {
          forEach(element.find('option'), function(option) {
            var shouldBeSelected = !!value && (includes(value, option.value) ||
                                               includes(value, selectCtrl.selectValueMap[option.value]));
            var currentlySelected = option.selected;

            // Support: IE 9-11 only, Edge 12-15+
            // In IE and Edge adding options to the selection via shift+click/UP/DOWN
            // will de-select already selected options if "selected" on those options was set
            // more than once (i.e. when the options were already selected)
            // So we only modify the selected property if necessary.
            // Note: this behavior cannot be replicated via unit tests because it only shows in the
            // actual user interface.
            if (shouldBeSelected !== currentlySelected) {
              setOptionSelectedStatus(jqLite(option), shouldBeSelected);
            }

          });
        };

        // we have to do it on each watch since ngModel watches reference, but
        // we need to work of an array, so we need to see if anything was inserted/removed
        var lastView, lastViewRef = NaN;
        scope.$watch(function selectMultipleWatch() {
          if (lastViewRef === ngModelCtrl.$viewValue && !equals(lastView, ngModelCtrl.$viewValue)) {
            lastView = shallowCopy(ngModelCtrl.$viewValue);
            ngModelCtrl.$render();
          }
          lastViewRef = ngModelCtrl.$viewValue;
        });

        // If we are a multiple select then value is now a collection
        // so the meaning of $isEmpty changes
        ngModelCtrl.$isEmpty = function(value) {
          return !value || value.length === 0;
        };

      }
    }

    function selectPostLink(scope, element, attrs, ctrls) {
      // if ngModel is not defined, we don't need to do anything
      var ngModelCtrl = ctrls[1];
      if (!ngModelCtrl) return;

      var selectCtrl = ctrls[0];

      // We delegate rendering to the `writeValue` method, which can be changed
      // if the select can have multiple selected values or if the options are being
      // generated by `ngOptions`.
      // This must be done in the postLink fn to prevent $render to be called before
      // all nodes have been linked correctly.
      ngModelCtrl.$render = function() {
        selectCtrl.writeValue(ngModelCtrl.$viewValue);
      };
    }
}
@ngdoc directive
@name select
@restrict E

@description
HTML `select` element with AngularJS data-binding.

The `select` directive is used together with {@link ngModel `ngModel`} to provide data-binding
between the scope and the `<select>` control (including setting default values).
It also handles dynamic `<option>` elements, which can be added using the {@link ngRepeat `ngRepeat}` or
{@link ngOptions `ngOptions`} directives.

When an item in the `<select>` menu is selected, the value of the selected option will be bound
to the model identified by the `ngModel` directive. With static or repeated options, this is
the content of the `value` attribute or the textContent of the `<option>`, if the value attribute is missing.
Value and textContent can be interpolated.

The {@link select.SelectController select controller} exposes utility functions that can be used
to manipulate the select's behavior.

## Matching model and option values

In general, the match between the model and an option is evaluated by strictly comparing the model
value against the value of the available options.

If you are setting the option value with the option's `value` attribute, or textContent, 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 correctly.

To bind the model to a non-string value, you can use one of the following strategies:
- the {@link ng.ngOptions `ngOptions`} directive
  ({@link ng.select#using-select-with-ngoptions-and-setting-a-default-value})
- the {@link ng.ngValue `ngValue`} directive, which allows arbitrary expressions to be
  option values ({@link ng.select#using-ngvalue-to-bind-the-model-to-an-array-of-objects Example})
- model $parsers / $formatters to convert the string value
  ({@link ng.select#binding-select-to-a-non-string-value-via-ngmodel-parsing-formatting Example})

If the viewValue of `ngModel` does not match any of the options, then the control
will automatically add an "unknown" option, which it then removes when the mismatch is resolved.

Optionally, a single hard-coded `<option>` element, with the value set to an empty string, can
be nested into the `<select>` element. This element will then represent the `null` or "not selected"
option. See example below for demonstration.

## Choosing between `ngRepeat` and `ngOptions`

In many cases, `ngRepeat` can be used on `<option>` elements instead of {@link ng.directive:ngOptions
ngOptions} to achieve a similar result. However, `ngOptions` provides some benefits:
- more flexibility in how the `<select>`'s model is assigned via the `select` **`as`** part of the
comprehension expression
- reduced memory consumption by not creating a new scope for each repeated instance
- increased render speed by creating the options in a documentFragment instead of individually

Specifically, select with repeated options slows down significantly starting at 2000 options in
Chrome and Internet Explorer / Edge.


@param {string} ngModel Assignable AngularJS expression to data-bind to.
@param {string=} name Property name of the form under which the control is published.
@param {string=} multiple Allows multiple options to be selected. The selected values will be
    bound to the model as an array.
@param {string=} required Sets `required` validation error key if the value is not entered.
@param {string=} ngRequired Adds required attribute and required validation constraint to
the element when the ngRequired expression evaluates to true. Use ngRequired instead of required
when you want to data-bind to the required attribute.
@param {string=} ngChange AngularJS expression to be executed when selected option(s) changes due to user
   interaction with the select element.
@param {string=} ngOptions sets the options that the select is populated with and defines what is
set on the model on selection. See {@link ngOptions `ngOptions`}.
@param {string=} ngAttrSize sets the size of the select element dynamically. Uses the
{@link guide/interpolation#-ngattr-for-binding-to-arbitrary-attributes ngAttr} directive.


@example
### Simple `select` elements with static options

<example name="static-select" module="staticSelect">
<file name="index.html">
<div ng-controller="ExampleController">
  <form name="myForm">
    <label for="singleSelect"> Single select: </label><br>
    <select name="singleSelect" ng-model="data.singleSelect">
      <option value="option-1">Option 1</option>
      <option value="option-2">Option 2</option>
    </select><br>

    <label for="singleSelect"> Single select with "not selected" option and dynamic option values: </label><br>
    <select name="singleSelect" id="singleSelect" ng-model="data.singleSelect">
      <option value="">---Please select---</option> <!-- not selected / blank option -->
      <option value="{{data.option1}}">Option 1</option> <!-- interpolation -->
      <option value="option-2">Option 2</option>
    </select><br>
    <button ng-click="forceUnknownOption()">Force unknown option</button><br>
    <tt>singleSelect = {{data.singleSelect}}</tt>

    <hr>
    <label for="multipleSelect"> Multiple select: </label><br>
    <select name="multipleSelect" id="multipleSelect" ng-model="data.multipleSelect" multiple>
      <option value="option-1">Option 1</option>
      <option value="option-2">Option 2</option>
      <option value="option-3">Option 3</option>
    </select><br>
    <tt>multipleSelect = {{data.multipleSelect}}</tt><br/>
  </form>
</div>
</file>
<file name="app.js">
 angular.module('staticSelect', [])
   .controller('ExampleController', ['$scope', function($scope) {
     $scope.data = {
      singleSelect: null,
      multipleSelect: [],
      option1: 'option-1'
     };

     $scope.forceUnknownOption = function() {
       $scope.data.singleSelect = 'nonsense';
     };
  }]);
</file>
</example>

@example
### Using `ngRepeat` to generate `select` options
<example name="select-ngrepeat" module="ngrepeatSelect">
<file name="index.html">
<div ng-controller="ExampleController">
  <form name="myForm">
    <label for="repeatSelect"> Repeat select: </label>
    <select name="repeatSelect" id="repeatSelect" ng-model="data.model">
      <option ng-repeat="option in data.availableOptions" value="{{option.id}}">{{option.name}}</option>
    </select>
  </form>
  <hr>
  <tt>model = {{data.model}}</tt><br/>
</div>
</file>
<file name="app.js">
 angular.module('ngrepeatSelect', [])
   .controller('ExampleController', ['$scope', function($scope) {
     $scope.data = {
      model: null,
      availableOptions: [
        {id: '1', name: 'Option A'},
        {id: '2', name: 'Option B'},
        {id: '3', name: 'Option C'}
      ]
     };
  }]);
</file>
</example>

@example
### Using `ngValue` to bind the model to an array of objects
<example name="select-ngvalue" module="ngvalueSelect">
<file name="index.html">
<div ng-controller="ExampleController">
  <form name="myForm">
    <label for="ngvalueselect"> ngvalue select: </label>
    <select size="6" name="ngvalueselect" ng-model="data.model" multiple>
      <option ng-repeat="option in data.availableOptions" ng-value="option.value">{{option.name}}</option>
    </select>
  </form>
  <hr>
  <pre>model = {{data.model | json}}</pre><br/>
</div>
</file>
<file name="app.js">
 angular.module('ngvalueSelect', [])
   .controller('ExampleController', ['$scope', function($scope) {
     $scope.data = {
      model: null,
      availableOptions: [
           {value: 'myString', name: 'string'},
           {value: 1, name: 'integer'},
           {value: true, name: 'boolean'},
           {value: null, name: 'null'},
           {value: {prop: 'value'}, name: 'object'},
           {value: ['a'], name: 'array'}
      ]
     };
  }]);
</file>
</example>

@example
### Using `select` with `ngOptions` and setting a default value
See the {@link ngOptions ngOptions documentation} for more `ngOptions` usage examples.

<example name="select-with-default-values" module="defaultValueSelect">
<file name="index.html">
<div ng-controller="ExampleController">
  <form name="myForm">
    <label for="mySelect">Make a choice:</label>
    <select name="mySelect" id="mySelect"
      ng-options="option.name for option in data.availableOptions track by option.id"
      ng-model="data.selectedOption"></select>
  </form>
  <hr>
  <tt>option = {{data.selectedOption}}</tt><br/>
</div>
</file>
<file name="app.js">
 angular.module('defaultValueSelect', [])
   .controller('ExampleController', ['$scope', function($scope) {
     $scope.data = {
      availableOptions: [
        {id: '1', name: 'Option A'},
        {id: '2', name: 'Option B'},
        {id: '3', name: 'Option C'}
      ],
      selectedOption: {id: '3', name: 'Option C'} //This sets the default value of the select in the ui
      };
  }]);
</file>
</example>

@example
### Binding `select` to a non-string value via `ngModel` parsing / formatting

<example name="select-with-non-string-options" module="nonStringSelect">
  <file name="index.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>
    {{ model }}
  </file>
  <file name="app.js">
    angular.module('nonStringSelect', [])
      .run(function($rootScope) {
        $rootScope.model = { id: 2 };
      })
      .directive('convertToNumber', function() {
        return {
          require: 'ngModel',
          link: function(scope, element, attrs, ngModel) {
            ngModel.$parsers.push(function(val) {
              return parseInt(val, 10);
            });
            ngModel.$formatters.push(function(val) {
              return '' + val;
            });
          }
        };
      });
  </file>
  <file name="protractor.js" type="protractor">
    it('should initialize to model', function() {
      expect(element(by.model('model.id')).$('option:checked').getText()).toEqual('Two');
    });
  </file>
</example>