selectDirective = function() {

  return {
    restrict: 'E',
    require: ['select', '?ngModel'],
    controller: SelectController,
    link: function(scope, element, attr, ctrls) {

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

      var selectCtrl = ctrls[0];

      selectCtrl.ngModelCtrl = ngModelCtrl;

      // 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`
      ngModelCtrl.$render = function() {
        selectCtrl.writeValue(ngModelCtrl.$viewValue);
      };

      // 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() {
        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) {

        // 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) {
              array.push(option.value);
            }
          });
          return array;
        };

        // Write value now needs to set the selected property of each matching option
        selectCtrl.writeValue = function writeMultipleValue(value) {
          var items = new HashMap(value);
          forEach(element.find('option'), function(option) {
            option.selected = isDefined(items.get(option.value));
          });
        };

        // 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;
        };

      }
    }
  };
}
@ngdoc directive
@name select
@restrict E

@description
HTML `SELECT` element with angular data-binding.

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 such as reducing
memory and increasing speed by not creating a new scope for each repeated instance, as well as providing
more flexibility in how the `<select>`'s model is assigned via the `select` **`as`** part of the
comprehension expression.

When an item in the `<select>` menu is selected, the array element or object property
represented by the selected option will be bound to the model identified by the `ngModel`
directive.

If the viewValue contains a value that doesn't match any of the options then the control
will automatically add an "unknown" option, which it then removes when this 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.

<div class="alert alert-info">
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 explictly convert it
using a directive (see example below) or use `ngOptions` to specify the set of options.
This is because an option element can only be bound to string values at present.
</div>

### Example (binding `select` to a non-string value)

<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() {
      var select = element(by.css('select'));
      expect(element(by.model('model.id')).$('option:checked').getText()).toEqual('Two');
    });
  </file>
</example>