fix(input): correctly handle invalid model values for input[date/time/…]

Similar to `input[number]` Angular will throw if the model value
for a `input[date]` is not a `Date` object.
For `Invalid Date`s (dates whose `getTime()` is `NaN`) `input[date]`
will render an empty string.

Closes #8949
Closes #9375
This commit is contained in:
Tobias Bosch
2014-10-01 15:24:44 -07:00
parent 3624e3800f
commit a0bfdd0d60
3 changed files with 60 additions and 28 deletions

View File

@@ -0,0 +1,11 @@
@ngdoc error
@name ngModel:datefmt
@fullName Model is not a date object
@description
All date-related inputs like `<input type="date">` require the model to be a `Date` object.
If the model is something else, this error will be thrown.
Angular does not set validation errors on the `<input>` in this case
as those errors are shown to the user, but the erroneous state was
caused by incorrect application logic and not by the user.

View File

@@ -1111,7 +1111,10 @@ function createDateInputType(type, regexp, parseDate, format) {
});
ctrl.$formatters.push(function(value) {
if (isDate(value)) {
if (!ctrl.$isEmpty(value)) {
if (!isDate(value)) {
throw $ngModelMinErr('datefmt', 'Expected `{0}` to be a date', value);
}
return $filter('date')(value, format, timezone);
}
return '';
@@ -1138,6 +1141,11 @@ function createDateInputType(type, regexp, parseDate, format) {
ctrl.$validate();
});
}
// Override the standard $isEmpty to detect invalid dates as well
ctrl.$isEmpty = function(value) {
// Invalid Date: getTime() returns NaN
return !value || (value.getTime && value.getTime() !== value.getTime());
};
function parseObservedDateValue(val) {
return isDefined(val) ? (isDate(val) ? val : parseDate(val)) : undefined;

View File

@@ -2251,14 +2251,14 @@ describe('input', function() {
// INPUT TYPES
describe('month', function (){
it('should render blank if model is not a Date object', function() {
it('should throw if model is not a Date object', function() {
compileInput('<input type="month" ng-model="january"/>');
scope.$apply(function(){
scope.january = '2013-01';
});
expect(inputElm.val()).toBe('');
expect(function() {
scope.$apply(function(){
scope.january = '2013-01';
});
}).toThrowMinErr('ngModel', 'datefmt', 'Expected `2013-01` to be a date');
});
it('should set the view if the model is a valid Date object', function (){
@@ -2433,14 +2433,14 @@ describe('input', function() {
});
describe('week', function (){
it('should set render blank if model is not a Date object', function() {
it('should throw if model is not a Date object', function() {
compileInput('<input type="week" ng-model="secondWeek"/>');
scope.$apply(function(){
scope.secondWeek = '2013-W02';
});
expect(inputElm.val()).toBe('');
expect(function() {
scope.$apply(function(){
scope.secondWeek = '2013-W02';
});
}).toThrowMinErr('ngModel', 'datefmt', 'Expected `2013-W02` to be a date');
});
it('should set the view if the model is a valid Date object', function (){
@@ -2615,14 +2615,14 @@ describe('input', function() {
});
describe('datetime-local', function () {
it('should render blank if model is not a Date object', function() {
it('should throw if model is not a Date object', function() {
compileInput('<input type="datetime-local" ng-model="lunchtime"/>');
scope.$apply(function(){
scope.lunchtime = '2013-12-16T11:30:00';
});
expect(inputElm.val()).toBe('');
expect(function() {
scope.$apply(function(){
scope.lunchtime = '2013-12-16T11:30:00';
});
}).toThrowMinErr('ngModel', 'datefmt', 'Expected `2013-12-16T11:30:00` to be a date');
});
it('should set the view if the model if a valid Date object.', function(){
@@ -2890,14 +2890,14 @@ describe('input', function() {
});
describe('time', function () {
it('should render blank if model is not a Date object', function() {
it('should throw if model is not a Date object', function() {
compileInput('<input type="time" ng-model="lunchtime"/>');
scope.$apply(function(){
scope.lunchtime = '11:30:00';
});
expect(inputElm.val()).toBe('');
expect(function() {
scope.$apply(function(){
scope.lunchtime = '11:30:00';
});
}).toThrowMinErr('ngModel', 'datefmt', 'Expected `11:30:00` to be a date');
});
it('should set the view if the model if a valid Date object.', function(){
@@ -3141,11 +3141,24 @@ describe('input', function() {
});
describe('date', function () {
it('should render blank if model is not a Date object.', function() {
it('should throw if model is not a Date object.', function() {
compileInput('<input type="date" ng-model="birthday"/>');
scope.$apply(function(){
scope.birthday = '1977-10-22';
expect(function() {
scope.$apply(function(){
scope.birthday = '1977-10-22';
});
}).toThrowMinErr('ngModel', 'datefmt', 'Expected `1977-10-22` to be a date');
});
it('should set the view to empty when the model is an InvalidDate', function() {
compileInput('<input type="date" ng-model="val"/>');
// reset the element type to text otherwise newer browsers
// would always set the input.value to empty for invalid dates...
inputElm.attr('type', 'text');
scope.$apply(function (){
scope.val = new Date('a');
});
expect(inputElm.val()).toBe('');