diff --git a/src/AngularPublic.js b/src/AngularPublic.js index c922c5e228ff..65ebe3f08716 100644 --- a/src/AngularPublic.js +++ b/src/AngularPublic.js @@ -101,7 +101,8 @@ function publishExternalAPI(angular){ ngChange: ngChangeDirective, ngModelInstant: ngModelInstantDirective, required: requiredDirective, - ngRequired: requiredDirective + ngRequired: requiredDirective, + ngValue: ngValueDirective }). directive(ngAttributeAliasDirectives). directive(ngEventDirectives); diff --git a/src/directive/input.js b/src/directive/input.js index c9553e395424..348c9f251128 100644 --- a/src/directive/input.js +++ b/src/directive/input.js @@ -558,7 +558,7 @@ function radioInputType(scope, element, attr, ctrl) { ctrl.$render = function() { var value = attr.value; - element[0].checked = isDefined(value) && (value == ctrl.$viewValue); + element[0].checked = (value == ctrl.$viewValue); }; attr.$observe('value', ctrl.$render); @@ -1168,3 +1168,27 @@ var ngListDirective = function() { } }; }; + + +var CONSTANT_VALUE_REGEXP = /^(true|false|\d+)$/; + +var ngValueDirective = [function() { + return { + priority: 100, + compile: function(tpl, attr) { + if (CONSTANT_VALUE_REGEXP.test(attr.ngValue)) { + return function(scope) { + attr.$set('value', scope.$eval(attr.ngValue)); + }; + } else { + attr.$observers.value = []; + + return function(scope) { + scope.$watch(attr.ngValue, function(value) { + attr.$set('value', value, false); + }); + }; + } + } + }; +}]; diff --git a/src/service/compiler.js b/src/service/compiler.js index 9e03f18626eb..8ddf77ae5614 100644 --- a/src/service/compiler.js +++ b/src/service/compiler.js @@ -732,7 +732,7 @@ function $CompileProvider($provide) { if (src[key]) { value += (key === 'style' ? ';' : ' ') + src[key]; } - dst.$set(key, value, srcAttr[key]); + dst.$set(key, value, true, srcAttr[key]); } }); // copy the new attributes on the old attrs object @@ -937,9 +937,11 @@ function $CompileProvider($provide) { * can share the attribute. This function properly handles boolean attributes. * @param {string} key Normalized key. (ie ngAttribute) * @param {string|boolean} value The value to set. If `null` attribute will be deleted. + * @param {boolean=} writeAttr If false, does not write the value to DOM element attribute. + * Defaults to true. * @param {string=} attrName Optional none normalized name. Defaults to key. */ - function attrSetter(key, value, attrName) { + function attrSetter(key, value, writeAttr, attrName) { var booleanKey = isBooleanAttr(this.$element[0], key.toLowerCase()); if (booleanKey) { @@ -962,12 +964,15 @@ function $CompileProvider($provide) { } } - if (value === null || value === undefined) { - this.$element.removeAttr(attrName); - } else { - this.$element.attr(attrName, value); + if (writeAttr !== false) { + if (value === null || value === undefined) { + this.$element.removeAttr(attrName); + } else { + this.$element.attr(attrName, value); + } } + // fire observers forEach(this.$observers[key], function(fn) { try { diff --git a/test/directive/inputSpec.js b/test/directive/inputSpec.js index 0b848df154d2..8d0e44b3eeb8 100644 --- a/test/directive/inputSpec.js +++ b/test/directive/inputSpec.js @@ -1078,4 +1078,42 @@ describe('input', function() { expect(scope.value).toBe('value3'); })); }); + + + describe('ng-value', function() { + + it('should evaluate and set constant expressions', function() { + compileInput('' + + '' + + ''); + scope.$digest(); + + browserTrigger(inputElm[0], 'click'); + expect(scope.selected).toBe(true); + + browserTrigger(inputElm[1], 'click'); + expect(scope.selected).toBe(false); + + browserTrigger(inputElm[2], 'click'); + expect(scope.selected).toBe(1); + }); + + + it('should watch the expression', function() { + compileInput(''); + + scope.$apply(function() { + scope.selected = scope.value = {some: 'object'}; + }); + expect(inputElm[0].checked).toBe(true); + + scope.$apply(function() { + scope.value = {some: 'other'}; + }); + expect(inputElm[0].checked).toBe(false); + + browserTrigger(inputElm, 'click'); + expect(scope.selected).toBe(scope.value); + }); + }); }); diff --git a/test/service/compilerSpec.js b/test/service/compilerSpec.js index d7ecdafc90b5..698fc23e0ae4 100644 --- a/test/service/compilerSpec.js +++ b/test/service/compilerSpec.js @@ -1401,7 +1401,7 @@ describe('$compile', function() { it('should allow overriding of attribute name and remember the name', function() { - attr.$set('ngOther', '123', 'other'); + attr.$set('ngOther', '123', true, 'other'); expect(element.attr('other')).toEqual('123'); expect(attr.ngOther).toEqual('123'); @@ -1437,7 +1437,15 @@ describe('$compile', function() { attr.$set('ngMyAttr', 'value'); attr.$set('ngMyAttr', null); expect(element.attr('ng-my-attr')).toBe(undefined); - }) + }); + + + it('should not set DOM element attr if writeAttr false', function() { + attr.$set('test', 'value', false); + + expect(element.attr('test')).toBeUndefined(); + expect(attr.test).toBe('value'); + }); }); });