Skip to content

Commit

Permalink
fix(input): correctly handle invalid model values for `input[date/tim…
Browse files Browse the repository at this point in the history
…e/…]`

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 angular#8949
Closes angular#9375
  • Loading branch information
tbosch committed Oct 1, 2014
1 parent 3624e38 commit a0bfdd0
Show file tree
Hide file tree
Showing 3 changed files with 60 additions and 28 deletions.
11 changes: 11 additions & 0 deletions docs/content/error/ngModel/datefmt.ngdoc
Original file line number Diff line number Diff line change
@@ -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.

10 changes: 9 additions & 1 deletion src/ng/directive/input.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 '';
Expand All @@ -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;
Expand Down
67 changes: 40 additions & 27 deletions test/ng/directive/inputSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 (){
Expand Down Expand Up @@ -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 (){
Expand Down Expand Up @@ -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(){
Expand Down Expand Up @@ -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(){
Expand Down Expand Up @@ -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('');
Expand Down

0 comments on commit a0bfdd0

Please sign in to comment.