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

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

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions docs/content/error/ngModel/constexpr.ngdoc
Original file line number Diff line number Diff line change
@@ -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}">
```
33 changes: 22 additions & 11 deletions src/ng/directive/input.js
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*
Expand All @@ -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>
Expand Down Expand Up @@ -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() {
Expand All @@ -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) {
Expand Down Expand Up @@ -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);
}
}
};
Expand Down
25 changes: 23 additions & 2 deletions test/ng/directive/inputSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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 />');

Expand Down