Skip to content

Commit 25bc169

Browse files
gkalpakpetebacondarwin
authored andcommitted
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 angular#15133 Closes angular#15149
1 parent 55f1517 commit 25bc169

File tree

2 files changed

+55
-44
lines changed

2 files changed

+55
-44
lines changed

src/ng/compile.js

+4-4
Original file line numberDiff line numberDiff line change
@@ -3221,16 +3221,16 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
32213221
}
32223222

32233223

3224-
function addAttrInterpolateDirective(node, directives, value, name, allOrNothing) {
3224+
function addAttrInterpolateDirective(node, directives, value, name, isNgAttr) {
32253225
var trustedContext = getTrustedContext(node, name);
3226-
allOrNothing = ALL_OR_NOTHING_ATTRS[name] || allOrNothing;
3226+
var mustHaveExpression = !isNgAttr;
3227+
var allOrNothing = ALL_OR_NOTHING_ATTRS[name] || isNgAttr;
32273228

3228-
var interpolateFn = $interpolate(value, true, trustedContext, allOrNothing);
3229+
var interpolateFn = $interpolate(value, mustHaveExpression, trustedContext, allOrNothing);
32293230

32303231
// no interpolation found -> ignore
32313232
if (!interpolateFn) return;
32323233

3233-
32343234
if (name === 'multiple' && nodeName_(node) === 'select') {
32353235
throw $compileMinErr('selmulti',
32363236
'Binding to the \'multiple\' attribute is not supported. Element: {0}',

test/ng/compileSpec.js

+51-40
Original file line numberDiff line numberDiff line change
@@ -10911,32 +10911,39 @@ describe('$compile', function() {
1091110911
}
1091210912

1091310913
describe('ngAttr* attribute binding', function() {
10914-
10915-
it('should bind after digest but not before', inject(function($compile, $rootScope) {
10914+
it('should bind after digest but not before', inject(function() {
1091610915
$rootScope.name = 'Misko';
1091710916
element = $compile('<span ng-attr-test="{{name}}"></span>')($rootScope);
1091810917
expect(element.attr('test')).toBeUndefined();
1091910918
$rootScope.$digest();
1092010919
expect(element.attr('test')).toBe('Misko');
1092110920
}));
1092210921

10923-
it('should bind after digest but not before when after overridden attribute', inject(function($compile, $rootScope) {
10922+
it('should bind after digest but not before when after overridden attribute', inject(function() {
1092410923
$rootScope.name = 'Misko';
1092510924
element = $compile('<span test="123" ng-attr-test="{{name}}"></span>')($rootScope);
1092610925
expect(element.attr('test')).toBe('123');
1092710926
$rootScope.$digest();
1092810927
expect(element.attr('test')).toBe('Misko');
1092910928
}));
1093010929

10931-
it('should bind after digest but not before when before overridden attribute', inject(function($compile, $rootScope) {
10930+
it('should bind after digest but not before when before overridden attribute', inject(function() {
1093210931
$rootScope.name = 'Misko';
1093310932
element = $compile('<span ng-attr-test="{{name}}" test="123"></span>')($rootScope);
1093410933
expect(element.attr('test')).toBe('123');
1093510934
$rootScope.$digest();
1093610935
expect(element.attr('test')).toBe('Misko');
1093710936
}));
1093810937

10939-
it('should remove attribute if any bindings are undefined', inject(function($compile, $rootScope) {
10938+
it('should set the attribute (after digest) even if there is no interpolation', inject(function() {
10939+
element = $compile('<span ng-attr-test="foo"></span>')($rootScope);
10940+
expect(element.attr('test')).toBeUndefined();
10941+
10942+
$rootScope.$digest();
10943+
expect(element.attr('test')).toBe('foo');
10944+
}));
10945+
10946+
it('should remove attribute if any bindings are undefined', inject(function() {
1094010947
element = $compile('<span ng-attr-test="{{name}}{{emphasis}}"></span>')($rootScope);
1094110948
$rootScope.$digest();
1094210949
expect(element.attr('test')).toBeUndefined();
@@ -10949,6 +10956,8 @@ describe('$compile', function() {
1094910956
}));
1095010957

1095110958
describe('in directive', function() {
10959+
var log;
10960+
1095210961
beforeEach(module(function() {
1095310962
directive('syncTest', function(log) {
1095410963
return {
@@ -10969,47 +10978,52 @@ describe('$compile', function() {
1096910978
});
1097010979
}));
1097110980

10972-
beforeEach(inject(function($templateCache) {
10981+
beforeEach(inject(function($templateCache, _log_) {
10982+
log = _log_;
1097310983
$templateCache.put('async.html', '<h1>Test</h1>');
1097410984
}));
1097510985

1097610986
it('should provide post-digest value in synchronous directive link functions when after overridden attribute',
10977-
inject(function(log, $rootScope, $compile) {
10978-
$rootScope.test = 'TEST';
10979-
element = $compile('<div sync-test test="123" ng-attr-test="{{test}}"></div>')($rootScope);
10980-
expect(element.attr('test')).toBe('123');
10981-
expect(log.toArray()).toEqual(['TEST', 'TEST']);
10982-
}));
10987+
function() {
10988+
$rootScope.test = 'TEST';
10989+
element = $compile('<div sync-test test="123" ng-attr-test="{{test}}"></div>')($rootScope);
10990+
expect(element.attr('test')).toBe('123');
10991+
expect(log.toArray()).toEqual(['TEST', 'TEST']);
10992+
}
10993+
);
1098310994

1098410995
it('should provide post-digest value in synchronous directive link functions when before overridden attribute',
10985-
inject(function(log, $rootScope, $compile) {
10986-
$rootScope.test = 'TEST';
10987-
element = $compile('<div sync-test ng-attr-test="{{test}}" test="123"></div>')($rootScope);
10988-
expect(element.attr('test')).toBe('123');
10989-
expect(log.toArray()).toEqual(['TEST', 'TEST']);
10990-
}));
10996+
function() {
10997+
$rootScope.test = 'TEST';
10998+
element = $compile('<div sync-test ng-attr-test="{{test}}" test="123"></div>')($rootScope);
10999+
expect(element.attr('test')).toBe('123');
11000+
expect(log.toArray()).toEqual(['TEST', 'TEST']);
11001+
}
11002+
);
1099111003

1099211004

1099311005
it('should provide post-digest value in asynchronous directive link functions when after overridden attribute',
10994-
inject(function(log, $rootScope, $compile) {
10995-
$rootScope.test = 'TEST';
10996-
element = $compile('<div async-test test="123" ng-attr-test="{{test}}"></div>')($rootScope);
10997-
expect(element.attr('test')).toBe('123');
10998-
$rootScope.$digest();
10999-
expect(log.toArray()).toEqual(['TEST', 'TEST']);
11000-
}));
11006+
function() {
11007+
$rootScope.test = 'TEST';
11008+
element = $compile('<div async-test test="123" ng-attr-test="{{test}}"></div>')($rootScope);
11009+
expect(element.attr('test')).toBe('123');
11010+
$rootScope.$digest();
11011+
expect(log.toArray()).toEqual(['TEST', 'TEST']);
11012+
}
11013+
);
1100111014

1100211015
it('should provide post-digest value in asynchronous directive link functions when before overridden attribute',
11003-
inject(function(log, $rootScope, $compile) {
11004-
$rootScope.test = 'TEST';
11005-
element = $compile('<div async-test ng-attr-test="{{test}}" test="123"></div>')($rootScope);
11006-
expect(element.attr('test')).toBe('123');
11007-
$rootScope.$digest();
11008-
expect(log.toArray()).toEqual(['TEST', 'TEST']);
11009-
}));
11016+
function() {
11017+
$rootScope.test = 'TEST';
11018+
element = $compile('<div async-test ng-attr-test="{{test}}" test="123"></div>')($rootScope);
11019+
expect(element.attr('test')).toBe('123');
11020+
$rootScope.$digest();
11021+
expect(log.toArray()).toEqual(['TEST', 'TEST']);
11022+
}
11023+
);
1101011024
});
1101111025

11012-
it('should work with different prefixes', inject(function($compile, $rootScope) {
11026+
it('should work with different prefixes', inject(function() {
1101311027
$rootScope.name = 'Misko';
1101411028
element = $compile('<span ng:attr:test="{{name}}" ng-Attr-test2="{{name}}" ng_Attr_test3="{{name}}"></span>')($rootScope);
1101511029
expect(element.attr('test')).toBeUndefined();
@@ -11021,14 +11035,14 @@ describe('$compile', function() {
1102111035
expect(element.attr('test3')).toBe('Misko');
1102211036
}));
1102311037

11024-
it('should work with the "href" attribute', inject(function($compile, $rootScope) {
11038+
it('should work with the "href" attribute', inject(function() {
1102511039
$rootScope.value = 'test';
1102611040
element = $compile('<a ng-attr-href="test/{{value}}"></a>')($rootScope);
1102711041
$rootScope.$digest();
1102811042
expect(element.attr('href')).toBe('test/test');
1102911043
}));
1103011044

11031-
it('should work if they are prefixed with x- or data- and different prefixes', inject(function($compile, $rootScope) {
11045+
it('should work if they are prefixed with x- or data- and different prefixes', inject(function() {
1103211046
$rootScope.name = 'Misko';
1103311047
element = $compile('<span data-ng-attr-test2="{{name}}" x-ng-attr-test3="{{name}}" data-ng:attr-test4="{{name}}" ' +
1103411048
'x_ng-attr-test5="{{name}}" data:ng-attr-test6="{{name}}"></span>')($rootScope);
@@ -11046,8 +11060,7 @@ describe('$compile', function() {
1104611060
}));
1104711061

1104811062
describe('when an attribute has a dash-separated name', function() {
11049-
11050-
it('should work with different prefixes', inject(function($compile, $rootScope) {
11063+
it('should work with different prefixes', inject(function() {
1105111064
$rootScope.name = 'JamieMason';
1105211065
element = $compile('<span ng:attr:dash-test="{{name}}" ng-Attr-dash-test2="{{name}}" ng_Attr_dash-test3="{{name}}"></span>')($rootScope);
1105311066
expect(element.attr('dash-test')).toBeUndefined();
@@ -11059,7 +11072,7 @@ describe('$compile', function() {
1105911072
expect(element.attr('dash-test3')).toBe('JamieMason');
1106011073
}));
1106111074

11062-
it('should work if they are prefixed with x- or data-', inject(function($compile, $rootScope) {
11075+
it('should work if they are prefixed with x- or data-', inject(function() {
1106311076
$rootScope.name = 'JamieMason';
1106411077
element = $compile('<span data-ng-attr-dash-test2="{{name}}" x-ng-attr-dash-test3="{{name}}" data-ng:attr-dash-test4="{{name}}"></span>')($rootScope);
1106511078
expect(element.attr('dash-test2')).toBeUndefined();
@@ -11088,7 +11101,6 @@ describe('$compile', function() {
1108811101
});
1108911102
});
1109011103

11091-
1109211104
it('should keep attributes ending with -end single-element directives', function() {
1109311105
module(function($compileProvider) {
1109411106
$compileProvider.directive('dashEnder', function(log) {
@@ -11106,7 +11118,6 @@ describe('$compile', function() {
1110611118
});
1110711119
});
1110811120
});
11109-
1111011121
});
1111111122

1111211123

0 commit comments

Comments
 (0)