Skip to content

Commit

Permalink
fix(ngModel): always format the viewValue as a string for text, url a…
Browse files Browse the repository at this point in the history
…nd email types

NgModel will format all scope-based values to string when setting the viewValue for
the associated input element. The formatting, however, only applies to input elements
that contain a text, email, url or blank input type. In the event of a null or undefined
scope or model value, the viewValue will be set to null or undefined instead of being
converted to an empty string.
  • Loading branch information
matsko committed Aug 29, 2014
1 parent 77ce5b8 commit 1eda183
Show file tree
Hide file tree
Showing 2 changed files with 78 additions and 4 deletions.
22 changes: 18 additions & 4 deletions src/ng/directive/input.js
Original file line number Diff line number Diff line change
Expand Up @@ -899,7 +899,18 @@ function testFlags(validity, flags) {
return false;
}

function stringBasedInputType(ctrl) {
ctrl.$formatters.push(function(value) {
return ctrl.$isEmpty(value) ? value : value.toString();
});
}

function textInputType(scope, element, attr, ctrl, $sniffer, $browser) {
baseInputType(scope, element, attr, ctrl, $sniffer, $browser);
stringBasedInputType(ctrl);
}

function baseInputType(scope, element, attr, ctrl, $sniffer, $browser) {
var validity = element.prop(VALIDITY_STATE_PROPERTY);
var placeholder = element[0].placeholder, noevent = {};
var type = lowercase(element[0].type);
Expand Down Expand Up @@ -1050,7 +1061,7 @@ function createDateParser(regexp, mapping) {
function createDateInputType(type, regexp, parseDate, format) {
return function dynamicDateInputType(scope, element, attr, ctrl, $sniffer, $browser, $filter) {
badInputChecker(scope, element, attr, ctrl);
textInputType(scope, element, attr, ctrl, $sniffer, $browser);
baseInputType(scope, element, attr, ctrl, $sniffer, $browser);
var timezone = ctrl && ctrl.$options && ctrl.$options.timezone;

ctrl.$$parserName = type;
Expand Down Expand Up @@ -1100,7 +1111,7 @@ function badInputChecker(scope, element, attr, ctrl) {

function numberInputType(scope, element, attr, ctrl, $sniffer, $browser) {
badInputChecker(scope, element, attr, ctrl);
textInputType(scope, element, attr, ctrl, $sniffer, $browser);
baseInputType(scope, element, attr, ctrl, $sniffer, $browser);

ctrl.$$parserName = 'number';
ctrl.$parsers.push(function(value) {
Expand Down Expand Up @@ -1134,7 +1145,8 @@ function numberInputType(scope, element, attr, ctrl, $sniffer, $browser) {

function urlInputType(scope, element, attr, ctrl, $sniffer, $browser) {
badInputChecker(scope, element, attr, ctrl);
textInputType(scope, element, attr, ctrl, $sniffer, $browser);
baseInputType(scope, element, attr, ctrl, $sniffer, $browser);
stringBasedInputType(ctrl);

ctrl.$$parserName = 'url';
ctrl.$validators.url = function(modelValue, viewValue) {
Expand All @@ -1145,7 +1157,8 @@ function urlInputType(scope, element, attr, ctrl, $sniffer, $browser) {

function emailInputType(scope, element, attr, ctrl, $sniffer, $browser) {
badInputChecker(scope, element, attr, ctrl);
textInputType(scope, element, attr, ctrl, $sniffer, $browser);
baseInputType(scope, element, attr, ctrl, $sniffer, $browser);
stringBasedInputType(ctrl);

ctrl.$$parserName = 'email';
ctrl.$validators.email = function(modelValue, viewValue) {
Expand Down Expand Up @@ -2585,6 +2598,7 @@ var minlengthDirective = function() {
var ngListDirective = function() {
return {
restrict: 'A',
priority: 100,
require: 'ngModel',
link: function(scope, element, attr, ctrl) {
// We want to control whitespace trimming so we use this convoluted approach
Expand Down
60 changes: 60 additions & 0 deletions test/ng/directive/inputSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -783,6 +783,66 @@ describe('ngModel', function() {
dealoc(element);
}));

it('should always format the viewValue as a string for a blank input type when the value is present',
inject(function($compile, $rootScope, $sniffer) {

var form = $compile('<form name="form"><input name="field" ng-model="val" /></form>')($rootScope);

$rootScope.val = 123;
$rootScope.$digest();
expect($rootScope.form.field.$viewValue).toBe('123');

$rootScope.val = null;
$rootScope.$digest();
expect($rootScope.form.field.$viewValue).toBe(null);

dealoc(form);
}));

it('should always format the viewValue as a string for a `text` input type when the value is present',
inject(function($compile, $rootScope, $sniffer) {

var form = $compile('<form name="form"><input type="text" name="field" ng-model="val" /></form>')($rootScope);
$rootScope.val = 123;
$rootScope.$digest();
expect($rootScope.form.field.$viewValue).toBe('123');

$rootScope.val = null;
$rootScope.$digest();
expect($rootScope.form.field.$viewValue).toBe(null);

dealoc(form);
}));

it('should always format the viewValue as a string for an `email` input type when the value is present',
inject(function($compile, $rootScope, $sniffer) {

var form = $compile('<form name="form"><input type="email" name="field" ng-model="val" /></form>')($rootScope);
$rootScope.val = 123;
$rootScope.$digest();
expect($rootScope.form.field.$viewValue).toBe('123');

$rootScope.val = null;
$rootScope.$digest();
expect($rootScope.form.field.$viewValue).toBe(null);

dealoc(form);
}));

it('should always format the viewValue as a string for a `url` input type when the value is present',
inject(function($compile, $rootScope, $sniffer) {

var form = $compile('<form name="form"><input type="url" name="field" ng-model="val" /></form>')($rootScope);
$rootScope.val = 123;
$rootScope.$digest();
expect($rootScope.form.field.$viewValue).toBe('123');

$rootScope.val = null;
$rootScope.$digest();
expect($rootScope.form.field.$viewValue).toBe(null);

dealoc(form);
}));

it('should set the control touched state on "blur" event', inject(function($compile, $rootScope) {
var element = $compile('<form name="myForm">' +
Expand Down

0 comments on commit 1eda183

Please sign in to comment.