fix($compile): set attribute value even if ngAttr* contains no interpolation

Previoulsy, when the value of an `ngAttrXyz` attribute did not contain any interpolation, then the
`xyz` attribute was never set.

BTW, this commit adds a negligible overhead (since we have to set up a one-time watcher for
example), but it is justifiable for someone that is using `ngAttrXyz` (instead of `xyz` directly).

(There is also some irrelevant refactoring to remove unnecessary dependencies from tests.)

Fixes #15133

Closes #15149
This commit is contained in:
Georgios Kalpakas
2016-09-16 17:25:24 +03:00
parent f60447072d
commit 3fe3da8794
2 changed files with 55 additions and 44 deletions
+4 -4
View File
@@ -3240,16 +3240,16 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
}
function addAttrInterpolateDirective(node, directives, value, name, allOrNothing) {
function addAttrInterpolateDirective(node, directives, value, name, isNgAttr) {
var trustedContext = getTrustedContext(node, name);
allOrNothing = ALL_OR_NOTHING_ATTRS[name] || allOrNothing;
var mustHaveExpression = !isNgAttr;
var allOrNothing = ALL_OR_NOTHING_ATTRS[name] || isNgAttr;
var interpolateFn = $interpolate(value, true, trustedContext, allOrNothing);
var interpolateFn = $interpolate(value, mustHaveExpression, trustedContext, allOrNothing);
// no interpolation found -> ignore
if (!interpolateFn) return;
if (name === 'multiple' && nodeName_(node) === 'select') {
throw $compileMinErr('selmulti',
'Binding to the \'multiple\' attribute is not supported. Element: {0}',
+51 -40
View File
@@ -11151,8 +11151,7 @@ describe('$compile', function() {
}
describe('ngAttr* attribute binding', function() {
it('should bind after digest but not before', inject(function($compile, $rootScope) {
it('should bind after digest but not before', inject(function() {
$rootScope.name = 'Misko';
element = $compile('<span ng-attr-test="{{name}}"></span>')($rootScope);
expect(element.attr('test')).toBeUndefined();
@@ -11160,7 +11159,7 @@ describe('$compile', function() {
expect(element.attr('test')).toBe('Misko');
}));
it('should bind after digest but not before when after overridden attribute', inject(function($compile, $rootScope) {
it('should bind after digest but not before when after overridden attribute', inject(function() {
$rootScope.name = 'Misko';
element = $compile('<span test="123" ng-attr-test="{{name}}"></span>')($rootScope);
expect(element.attr('test')).toBe('123');
@@ -11168,7 +11167,7 @@ describe('$compile', function() {
expect(element.attr('test')).toBe('Misko');
}));
it('should bind after digest but not before when before overridden attribute', inject(function($compile, $rootScope) {
it('should bind after digest but not before when before overridden attribute', inject(function() {
$rootScope.name = 'Misko';
element = $compile('<span ng-attr-test="{{name}}" test="123"></span>')($rootScope);
expect(element.attr('test')).toBe('123');
@@ -11176,7 +11175,15 @@ describe('$compile', function() {
expect(element.attr('test')).toBe('Misko');
}));
it('should remove attribute if any bindings are undefined', inject(function($compile, $rootScope) {
it('should set the attribute (after digest) even if there is no interpolation', inject(function() {
element = $compile('<span ng-attr-test="foo"></span>')($rootScope);
expect(element.attr('test')).toBeUndefined();
$rootScope.$digest();
expect(element.attr('test')).toBe('foo');
}));
it('should remove attribute if any bindings are undefined', inject(function() {
element = $compile('<span ng-attr-test="{{name}}{{emphasis}}"></span>')($rootScope);
$rootScope.$digest();
expect(element.attr('test')).toBeUndefined();
@@ -11189,6 +11196,8 @@ describe('$compile', function() {
}));
describe('in directive', function() {
var log;
beforeEach(module(function() {
directive('syncTest', function(log) {
return {
@@ -11209,47 +11218,52 @@ describe('$compile', function() {
});
}));
beforeEach(inject(function($templateCache) {
beforeEach(inject(function($templateCache, _log_) {
log = _log_;
$templateCache.put('async.html', '<h1>Test</h1>');
}));
it('should provide post-digest value in synchronous directive link functions when after overridden attribute',
inject(function(log, $rootScope, $compile) {
$rootScope.test = 'TEST';
element = $compile('<div sync-test test="123" ng-attr-test="{{test}}"></div>')($rootScope);
expect(element.attr('test')).toBe('123');
expect(log.toArray()).toEqual(['TEST', 'TEST']);
}));
function() {
$rootScope.test = 'TEST';
element = $compile('<div sync-test test="123" ng-attr-test="{{test}}"></div>')($rootScope);
expect(element.attr('test')).toBe('123');
expect(log.toArray()).toEqual(['TEST', 'TEST']);
}
);
it('should provide post-digest value in synchronous directive link functions when before overridden attribute',
inject(function(log, $rootScope, $compile) {
$rootScope.test = 'TEST';
element = $compile('<div sync-test ng-attr-test="{{test}}" test="123"></div>')($rootScope);
expect(element.attr('test')).toBe('123');
expect(log.toArray()).toEqual(['TEST', 'TEST']);
}));
function() {
$rootScope.test = 'TEST';
element = $compile('<div sync-test ng-attr-test="{{test}}" test="123"></div>')($rootScope);
expect(element.attr('test')).toBe('123');
expect(log.toArray()).toEqual(['TEST', 'TEST']);
}
);
it('should provide post-digest value in asynchronous directive link functions when after overridden attribute',
inject(function(log, $rootScope, $compile) {
$rootScope.test = 'TEST';
element = $compile('<div async-test test="123" ng-attr-test="{{test}}"></div>')($rootScope);
expect(element.attr('test')).toBe('123');
$rootScope.$digest();
expect(log.toArray()).toEqual(['TEST', 'TEST']);
}));
function() {
$rootScope.test = 'TEST';
element = $compile('<div async-test test="123" ng-attr-test="{{test}}"></div>')($rootScope);
expect(element.attr('test')).toBe('123');
$rootScope.$digest();
expect(log.toArray()).toEqual(['TEST', 'TEST']);
}
);
it('should provide post-digest value in asynchronous directive link functions when before overridden attribute',
inject(function(log, $rootScope, $compile) {
$rootScope.test = 'TEST';
element = $compile('<div async-test ng-attr-test="{{test}}" test="123"></div>')($rootScope);
expect(element.attr('test')).toBe('123');
$rootScope.$digest();
expect(log.toArray()).toEqual(['TEST', 'TEST']);
}));
function() {
$rootScope.test = 'TEST';
element = $compile('<div async-test ng-attr-test="{{test}}" test="123"></div>')($rootScope);
expect(element.attr('test')).toBe('123');
$rootScope.$digest();
expect(log.toArray()).toEqual(['TEST', 'TEST']);
}
);
});
it('should work with different prefixes', inject(function($compile, $rootScope) {
it('should work with different prefixes', inject(function() {
$rootScope.name = 'Misko';
element = $compile('<span ng:attr:test="{{name}}" ng-Attr-test2="{{name}}" ng_Attr_test3="{{name}}"></span>')($rootScope);
expect(element.attr('test')).toBeUndefined();
@@ -11261,14 +11275,14 @@ describe('$compile', function() {
expect(element.attr('test3')).toBe('Misko');
}));
it('should work with the "href" attribute', inject(function($compile, $rootScope) {
it('should work with the "href" attribute', inject(function() {
$rootScope.value = 'test';
element = $compile('<a ng-attr-href="test/{{value}}"></a>')($rootScope);
$rootScope.$digest();
expect(element.attr('href')).toBe('test/test');
}));
it('should work if they are prefixed with x- or data- and different prefixes', inject(function($compile, $rootScope) {
it('should work if they are prefixed with x- or data- and different prefixes', inject(function() {
$rootScope.name = 'Misko';
element = $compile('<span data-ng-attr-test2="{{name}}" x-ng-attr-test3="{{name}}" data-ng:attr-test4="{{name}}" ' +
'x_ng-attr-test5="{{name}}" data:ng-attr-test6="{{name}}"></span>')($rootScope);
@@ -11286,8 +11300,7 @@ describe('$compile', function() {
}));
describe('when an attribute has a dash-separated name', function() {
it('should work with different prefixes', inject(function($compile, $rootScope) {
it('should work with different prefixes', inject(function() {
$rootScope.name = 'JamieMason';
element = $compile('<span ng:attr:dash-test="{{name}}" ng-Attr-dash-test2="{{name}}" ng_Attr_dash-test3="{{name}}"></span>')($rootScope);
expect(element.attr('dash-test')).toBeUndefined();
@@ -11299,7 +11312,7 @@ describe('$compile', function() {
expect(element.attr('dash-test3')).toBe('JamieMason');
}));
it('should work if they are prefixed with x- or data-', inject(function($compile, $rootScope) {
it('should work if they are prefixed with x- or data-', inject(function() {
$rootScope.name = 'JamieMason';
element = $compile('<span data-ng-attr-dash-test2="{{name}}" x-ng-attr-dash-test3="{{name}}" data-ng:attr-dash-test4="{{name}}"></span>')($rootScope);
expect(element.attr('dash-test2')).toBeUndefined();
@@ -11328,7 +11341,6 @@ describe('$compile', function() {
});
});
it('should keep attributes ending with -end single-element directives', function() {
module(function($compileProvider) {
$compileProvider.directive('dashEnder', function(log) {
@@ -11346,7 +11358,6 @@ describe('$compile', function() {
});
});
});
});