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

Commit a0bfdd0

Browse files
committed
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
1 parent 3624e38 commit a0bfdd0

File tree

3 files changed

+60
-28
lines changed

3 files changed

+60
-28
lines changed
+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
@ngdoc error
2+
@name ngModel:datefmt
3+
@fullName Model is not a date object
4+
@description
5+
6+
All date-related inputs like `<input type="date">` require the model to be a `Date` object.
7+
If the model is something else, this error will be thrown.
8+
Angular does not set validation errors on the `<input>` in this case
9+
as those errors are shown to the user, but the erroneous state was
10+
caused by incorrect application logic and not by the user.
11+

src/ng/directive/input.js

+9-1
Original file line numberDiff line numberDiff line change
@@ -1111,7 +1111,10 @@ function createDateInputType(type, regexp, parseDate, format) {
11111111
});
11121112

11131113
ctrl.$formatters.push(function(value) {
1114-
if (isDate(value)) {
1114+
if (!ctrl.$isEmpty(value)) {
1115+
if (!isDate(value)) {
1116+
throw $ngModelMinErr('datefmt', 'Expected `{0}` to be a date', value);
1117+
}
11151118
return $filter('date')(value, format, timezone);
11161119
}
11171120
return '';
@@ -1138,6 +1141,11 @@ function createDateInputType(type, regexp, parseDate, format) {
11381141
ctrl.$validate();
11391142
});
11401143
}
1144+
// Override the standard $isEmpty to detect invalid dates as well
1145+
ctrl.$isEmpty = function(value) {
1146+
// Invalid Date: getTime() returns NaN
1147+
return !value || (value.getTime && value.getTime() !== value.getTime());
1148+
};
11411149

11421150
function parseObservedDateValue(val) {
11431151
return isDefined(val) ? (isDate(val) ? val : parseDate(val)) : undefined;

test/ng/directive/inputSpec.js

+40-27
Original file line numberDiff line numberDiff line change
@@ -2251,14 +2251,14 @@ describe('input', function() {
22512251

22522252
// INPUT TYPES
22532253
describe('month', function (){
2254-
it('should render blank if model is not a Date object', function() {
2254+
it('should throw if model is not a Date object', function() {
22552255
compileInput('<input type="month" ng-model="january"/>');
22562256

2257-
scope.$apply(function(){
2258-
scope.january = '2013-01';
2259-
});
2260-
2261-
expect(inputElm.val()).toBe('');
2257+
expect(function() {
2258+
scope.$apply(function(){
2259+
scope.january = '2013-01';
2260+
});
2261+
}).toThrowMinErr('ngModel', 'datefmt', 'Expected `2013-01` to be a date');
22622262
});
22632263

22642264
it('should set the view if the model is a valid Date object', function (){
@@ -2433,14 +2433,14 @@ describe('input', function() {
24332433
});
24342434

24352435
describe('week', function (){
2436-
it('should set render blank if model is not a Date object', function() {
2436+
it('should throw if model is not a Date object', function() {
24372437
compileInput('<input type="week" ng-model="secondWeek"/>');
24382438

2439-
scope.$apply(function(){
2440-
scope.secondWeek = '2013-W02';
2441-
});
2442-
2443-
expect(inputElm.val()).toBe('');
2439+
expect(function() {
2440+
scope.$apply(function(){
2441+
scope.secondWeek = '2013-W02';
2442+
});
2443+
}).toThrowMinErr('ngModel', 'datefmt', 'Expected `2013-W02` to be a date');
24442444
});
24452445

24462446
it('should set the view if the model is a valid Date object', function (){
@@ -2615,14 +2615,14 @@ describe('input', function() {
26152615
});
26162616

26172617
describe('datetime-local', function () {
2618-
it('should render blank if model is not a Date object', function() {
2618+
it('should throw if model is not a Date object', function() {
26192619
compileInput('<input type="datetime-local" ng-model="lunchtime"/>');
26202620

2621-
scope.$apply(function(){
2622-
scope.lunchtime = '2013-12-16T11:30:00';
2623-
});
2624-
2625-
expect(inputElm.val()).toBe('');
2621+
expect(function() {
2622+
scope.$apply(function(){
2623+
scope.lunchtime = '2013-12-16T11:30:00';
2624+
});
2625+
}).toThrowMinErr('ngModel', 'datefmt', 'Expected `2013-12-16T11:30:00` to be a date');
26262626
});
26272627

26282628
it('should set the view if the model if a valid Date object.', function(){
@@ -2890,14 +2890,14 @@ describe('input', function() {
28902890
});
28912891

28922892
describe('time', function () {
2893-
it('should render blank if model is not a Date object', function() {
2893+
it('should throw if model is not a Date object', function() {
28942894
compileInput('<input type="time" ng-model="lunchtime"/>');
28952895

2896-
scope.$apply(function(){
2897-
scope.lunchtime = '11:30:00';
2898-
});
2899-
2900-
expect(inputElm.val()).toBe('');
2896+
expect(function() {
2897+
scope.$apply(function(){
2898+
scope.lunchtime = '11:30:00';
2899+
});
2900+
}).toThrowMinErr('ngModel', 'datefmt', 'Expected `11:30:00` to be a date');
29012901
});
29022902

29032903
it('should set the view if the model if a valid Date object.', function(){
@@ -3141,11 +3141,24 @@ describe('input', function() {
31413141
});
31423142

31433143
describe('date', function () {
3144-
it('should render blank if model is not a Date object.', function() {
3144+
it('should throw if model is not a Date object.', function() {
31453145
compileInput('<input type="date" ng-model="birthday"/>');
31463146

3147-
scope.$apply(function(){
3148-
scope.birthday = '1977-10-22';
3147+
expect(function() {
3148+
scope.$apply(function(){
3149+
scope.birthday = '1977-10-22';
3150+
});
3151+
}).toThrowMinErr('ngModel', 'datefmt', 'Expected `1977-10-22` to be a date');
3152+
});
3153+
3154+
it('should set the view to empty when the model is an InvalidDate', function() {
3155+
compileInput('<input type="date" ng-model="val"/>');
3156+
// reset the element type to text otherwise newer browsers
3157+
// would always set the input.value to empty for invalid dates...
3158+
inputElm.attr('type', 'text');
3159+
3160+
scope.$apply(function (){
3161+
scope.val = new Date('a');
31493162
});
31503163

31513164
expect(inputElm.val()).toBe('');

0 commit comments

Comments
 (0)