feat(input): support constant expressions for ngTrueValue/ngFalseValue

ngTrueValue and ngFalseValue now support parsed expressions which the parser determines to be constant values.

BREAKING CHANGE:

Previously, these attributes would always be treated as strings. However, they are now parsed as
expressions, and will throw if an expression is non-constant.

To convert non-constant strings into constant expressions, simply wrap them in an extra pair of quotes, like so:

    <input type="checkbox" ng-model="..." ng-true-value="'truthyValue'">

Closes #8041
Closes #5346
Closes #1199
This commit is contained in:
Caitlin Potter
2014-07-01 20:24:58 -04:00
parent 81214498ab
commit c90cefe161
3 changed files with 66 additions and 13 deletions

View File

@@ -0,0 +1,21 @@
@ngdoc error
@name ngModel:constexpr
@fullName Non-Constant Expression
@description
Some attributes used in conjunction with ngModel (such as ngTrueValue or ngFalseValue) will only
accept constant expressions.
Examples using constant expressions include:
```
<input type="checkbox" ng-model="..." ng-true-value="'truthyValue'">
<input type="checkbox" ng-model="..." ng-false-value="0">
```
Examples of non-constant expressions include:
```
<input type="checkbox" ng-model="..." ng-true-value="someValue">
<input type="checkbox" ng-model="..." ng-false-value="{foo: someScopeValue}">
```

View File

@@ -807,8 +807,8 @@ var inputType = {
*
* @param {string} ngModel Assignable angular expression to data-bind to.
* @param {string=} name Property name of the form under which the control is published.
* @param {string=} ngTrueValue The value to which the expression should be set when selected.
* @param {string=} ngFalseValue The value to which the expression should be set when not selected.
* @param {expression=} ngTrueValue The value to which the expression should be set when selected.
* @param {expression=} ngFalseValue The value to which the expression should be set when not selected.
* @param {string=} ngChange Angular expression to be executed when input changes due to user
* interaction with the input element.
*
@@ -824,7 +824,7 @@ var inputType = {
<form name="myForm" ng-controller="Ctrl">
Value1: <input type="checkbox" ng-model="value1"> <br/>
Value2: <input type="checkbox" ng-model="value2"
ng-true-value="YES" ng-false-value="NO"> <br/>
ng-true-value="'YES'" ng-false-value="'NO'"> <br/>
<tt>value1 = {{value1}}</tt><br/>
<tt>value2 = {{value2}}</tt><br/>
</form>
@@ -1183,12 +1183,22 @@ function radioInputType(scope, element, attr, ctrl) {
attr.$observe('value', ctrl.$render);
}
function checkboxInputType(scope, element, attr, ctrl) {
var trueValue = attr.ngTrueValue,
falseValue = attr.ngFalseValue;
function parseConstantExpr($parse, context, name, expression, fallback) {
var parseFn;
if (isDefined(expression)) {
parseFn = $parse(expression);
if (!parseFn.constant) {
throw new minErr('ngModel')('constexpr', 'Expected constant expression for `{0}`, but saw ' +
'`{1}`.', name, expression);
}
return parseFn(context);
}
return fallback;
}
if (!isString(trueValue)) trueValue = true;
if (!isString(falseValue)) falseValue = false;
function checkboxInputType(scope, element, attr, ctrl, $sniffer, $browser, $filter, $parse) {
var trueValue = parseConstantExpr($parse, scope, 'ngTrueValue', attr.ngTrueValue, true);
var falseValue = parseConstantExpr($parse, scope, 'ngFalseValue', attr.ngFalseValue, false);
var listener = function(ev) {
scope.$apply(function() {
@@ -1208,7 +1218,7 @@ function checkboxInputType(scope, element, attr, ctrl) {
};
ctrl.$formatters.push(function(value) {
return value === trueValue;
return equals(value, trueValue);
});
ctrl.$parsers.push(function(value) {
@@ -1356,14 +1366,15 @@ function checkboxInputType(scope, element, attr, ctrl) {
</file>
</example>
*/
var inputDirective = ['$browser', '$sniffer', '$filter', function($browser, $sniffer, $filter) {
var inputDirective = ['$browser', '$sniffer', '$filter', '$parse',
function($browser, $sniffer, $filter, $parse) {
return {
restrict: 'E',
require: ['?ngModel'],
link: function(scope, element, attr, ctrls) {
if (ctrls[0]) {
(inputType[lowercase(attr.type)] || inputType.text)(scope, element, attr, ctrls[0], $sniffer,
$browser, $filter);
$browser, $filter, $parse);
}
}
};

View File

@@ -2528,8 +2528,8 @@ describe('input', function() {
it('should allow custom enumeration', function() {
compileInput('<input type="checkbox" ng-model="name" ng-true-value="y" ' +
'ng-false-value="n">');
compileInput('<input type="checkbox" ng-model="name" ng-true-value="\'y\'" ' +
'ng-false-value="\'n\'">');
scope.$apply(function() {
scope.name = 'y';
@@ -2554,6 +2554,27 @@ describe('input', function() {
});
it('should throw if ngTrueValue is present and not a constant expression', function() {
expect(function() {
compileInput('<input type="checkbox" ng-model="value" ng-true-value="yes" />');
}).toThrowMinErr('ngModel', 'constexpr', "Expected constant expression for `ngTrueValue`, but saw `yes`.");
});
it('should throw if ngFalseValue is present and not a constant expression', function() {
expect(function() {
compileInput('<input type="checkbox" ng-model="value" ng-false-value="no" />');
}).toThrowMinErr('ngModel', 'constexpr', "Expected constant expression for `ngFalseValue`, but saw `no`.");
});
it('should not throw if ngTrueValue or ngFalseValue are not present', function() {
expect(function() {
compileInput('<input type="checkbox" ng-model="value" />');
}).not.toThrow();
});
it('should be required if false', function() {
compileInput('<input type="checkbox" ng:model="value" required />');