Skip to content
This repository was archived by the owner on Sep 5, 2024. It is now read-only.

Commit 8758488

Browse files
devversionThomasBurleson
authored andcommitted
fix(select): ngModel validator should use option hashkeys.
* Currently when using `required` on the select, is adding the required validator to the ngModelCtrl. The required validator is checking the `$viewValue`by using the `$isEmpty` function, which is overwritten by the select component. `$isEmpty` normally checks just for truthy values, but our custom `$isEmpty` function checks the value for being a registered option. This does not work, because we are just trying to find the value in the options object. Once the the value is also an object, we are not able to find the option (which is wrong). All options inside of the variable are stored within their associated hashkey. That's why we have to retrieve the hashkey of the current value. This is also required for ngModel's with a `trackBy` option. * Once an option is set to the ngModel, before the actual `md-option` directive is registered, the required validator will be set to `false`. We have to trigger a new validation, when the option, which is currently the $modelValue, has been registered. Fixes #8666. Closes #8763
1 parent 121a39d commit 8758488

File tree

2 files changed

+43
-1
lines changed

2 files changed

+43
-1
lines changed

src/components/select/select.js

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -685,7 +685,9 @@ function SelectMenuDirective($parse, $mdUtil, $mdTheming) {
685685

686686
// Setup a more robust version of isEmpty to ensure value is a valid option
687687
self.ngModel.$isEmpty = function($viewValue) {
688-
return !self.options[$viewValue];
688+
// We have to transform the viewValue into the hashKey, because otherwise the
689+
// OptionCtrl may not exist. Developers may have specified a trackBy function.
690+
return !self.options[self.hashGetter($viewValue)];
689691
};
690692

691693
// Allow users to provide `ng-model="foo" ng-model-options="{trackBy: 'foo.id'}"` so
@@ -763,11 +765,21 @@ function SelectMenuDirective($parse, $mdUtil, $mdTheming) {
763765
throw new Error('Duplicate md-option values are not allowed in a select. ' +
764766
'Duplicate value "' + optionCtrl.value + '" found.');
765767
}
768+
766769
self.options[hashKey] = optionCtrl;
767770

768771
// If this option's value was already in our ngModel, go ahead and select it.
769772
if (angular.isDefined(self.selected[hashKey])) {
770773
self.select(hashKey, optionCtrl.value);
774+
775+
// When the current $modelValue of the ngModel Controller is using the same hash as
776+
// the current option, which will be added, then we can be sure, that the validation
777+
// of the option has occurred before the option was added properly.
778+
// This means, that we have to manually trigger a new validation of the current option.
779+
if (self.ngModel.$modelValue && self.hashGetter(self.ngModel.$modelValue) === hashKey) {
780+
self.ngModel.$validate();
781+
}
782+
771783
self.refreshViewValue();
772784
}
773785
};

src/components/select/select.spec.js

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -883,6 +883,36 @@ describe('<md-select>', function() {
883883
expect($rootScope.testForm.$valid).toBe(true);
884884
}));
885885

886+
it('properly validates required attribute with object options', inject(function($rootScope, $compile) {
887+
var template =
888+
'<form name="testForm">' +
889+
' <md-select ng-model="model" ng-model-options="{ trackBy: \'$value.id\' }" required="required">' +
890+
' <md-option ng-repeat="opt in opts" ng-value="opt"></md-option>' +
891+
' </md-select>' +
892+
'</form>';
893+
894+
$rootScope.opts = [
895+
{ id: 1, value: 'First' },
896+
{ id: 2, value: 'Second' },
897+
{ id: 3, value: 'Third' },
898+
{ id: 4, value: 'Fourth' }
899+
];
900+
901+
$compile(template)($rootScope);
902+
903+
// There is no value selected yet, so the validation should currently fail.
904+
$rootScope.$digest();
905+
906+
expect($rootScope.testForm.$valid).toBe(false);
907+
908+
// Select any valid option, to confirm that the ngModel properly detects the
909+
// tracked option.
910+
$rootScope.model = $rootScope.opts[0];
911+
$rootScope.$digest();
912+
913+
expect($rootScope.testForm.$valid).toBe(true);
914+
}));
915+
886916
it('should keep the form pristine when model is predefined', inject(function($rootScope, $timeout, $compile) {
887917
$rootScope.model = [1, 2];
888918
$rootScope.opts = [1, 2, 3, 4];

0 commit comments

Comments
 (0)