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

Commit 3fe3da8

Browse files
committed
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
1 parent f604470 commit 3fe3da8

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
@@ -3240,16 +3240,16 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
32403240
}
32413241

32423242

3243-
function addAttrInterpolateDirective(node, directives, value, name, allOrNothing) {
3243+
function addAttrInterpolateDirective(node, directives, value, name, isNgAttr) {
32443244
var trustedContext = getTrustedContext(node, name);
3245-
allOrNothing = ALL_OR_NOTHING_ATTRS[name] || allOrNothing;
3245+
var mustHaveExpression = !isNgAttr;
3246+
var allOrNothing = ALL_OR_NOTHING_ATTRS[name] || isNgAttr;
32463247

3247-
var interpolateFn = $interpolate(value, true, trustedContext, allOrNothing);
3248+
var interpolateFn = $interpolate(value, mustHaveExpression, trustedContext, allOrNothing);
32483249

32493250
// no interpolation found -> ignore
32503251
if (!interpolateFn) return;
32513252

3252-
32533253
if (name === 'multiple' && nodeName_(node) === 'select') {
32543254
throw $compileMinErr('selmulti',
32553255
'Binding to the \'multiple\' attribute is not supported. Element: {0}',

test/ng/compileSpec.js

+51-40
Original file line numberDiff line numberDiff line change
@@ -11151,32 +11151,39 @@ describe('$compile', function() {
1115111151
}
1115211152

1115311153
describe('ngAttr* attribute binding', function() {
11154-
11155-
it('should bind after digest but not before', inject(function($compile, $rootScope) {
11154+
it('should bind after digest but not before', inject(function() {
1115611155
$rootScope.name = 'Misko';
1115711156
element = $compile('<span ng-attr-test="{{name}}"></span>')($rootScope);
1115811157
expect(element.attr('test')).toBeUndefined();
1115911158
$rootScope.$digest();
1116011159
expect(element.attr('test')).toBe('Misko');
1116111160
}));
1116211161

11163-
it('should bind after digest but not before when after overridden attribute', inject(function($compile, $rootScope) {
11162+
it('should bind after digest but not before when after overridden attribute', inject(function() {
1116411163
$rootScope.name = 'Misko';
1116511164
element = $compile('<span test="123" ng-attr-test="{{name}}"></span>')($rootScope);
1116611165
expect(element.attr('test')).toBe('123');
1116711166
$rootScope.$digest();
1116811167
expect(element.attr('test')).toBe('Misko');
1116911168
}));
1117011169

11171-
it('should bind after digest but not before when before overridden attribute', inject(function($compile, $rootScope) {
11170+
it('should bind after digest but not before when before overridden attribute', inject(function() {
1117211171
$rootScope.name = 'Misko';
1117311172
element = $compile('<span ng-attr-test="{{name}}" test="123"></span>')($rootScope);
1117411173
expect(element.attr('test')).toBe('123');
1117511174
$rootScope.$digest();
1117611175
expect(element.attr('test')).toBe('Misko');
1117711176
}));
1117811177

11179-
it('should remove attribute if any bindings are undefined', inject(function($compile, $rootScope) {
11178+
it('should set the attribute (after digest) even if there is no interpolation', inject(function() {
11179+
element = $compile('<span ng-attr-test="foo"></span>')($rootScope);
11180+
expect(element.attr('test')).toBeUndefined();
11181+
11182+
$rootScope.$digest();
11183+
expect(element.attr('test')).toBe('foo');
11184+
}));
11185+
11186+
it('should remove attribute if any bindings are undefined', inject(function() {
1118011187
element = $compile('<span ng-attr-test="{{name}}{{emphasis}}"></span>')($rootScope);
1118111188
$rootScope.$digest();
1118211189
expect(element.attr('test')).toBeUndefined();
@@ -11189,6 +11196,8 @@ describe('$compile', function() {
1118911196
}));
1119011197

1119111198
describe('in directive', function() {
11199+
var log;
11200+
1119211201
beforeEach(module(function() {
1119311202
directive('syncTest', function(log) {
1119411203
return {
@@ -11209,47 +11218,52 @@ describe('$compile', function() {
1120911218
});
1121011219
}));
1121111220

11212-
beforeEach(inject(function($templateCache) {
11221+
beforeEach(inject(function($templateCache, _log_) {
11222+
log = _log_;
1121311223
$templateCache.put('async.html', '<h1>Test</h1>');
1121411224
}));
1121511225

1121611226
it('should provide post-digest value in synchronous directive link functions when after overridden attribute',
11217-
inject(function(log, $rootScope, $compile) {
11218-
$rootScope.test = 'TEST';
11219-
element = $compile('<div sync-test test="123" ng-attr-test="{{test}}"></div>')($rootScope);
11220-
expect(element.attr('test')).toBe('123');
11221-
expect(log.toArray()).toEqual(['TEST', 'TEST']);
11222-
}));
11227+
function() {
11228+
$rootScope.test = 'TEST';
11229+
element = $compile('<div sync-test test="123" ng-attr-test="{{test}}"></div>')($rootScope);
11230+
expect(element.attr('test')).toBe('123');
11231+
expect(log.toArray()).toEqual(['TEST', 'TEST']);
11232+
}
11233+
);
1122311234

1122411235
it('should provide post-digest value in synchronous directive link functions when before overridden attribute',
11225-
inject(function(log, $rootScope, $compile) {
11226-
$rootScope.test = 'TEST';
11227-
element = $compile('<div sync-test ng-attr-test="{{test}}" test="123"></div>')($rootScope);
11228-
expect(element.attr('test')).toBe('123');
11229-
expect(log.toArray()).toEqual(['TEST', 'TEST']);
11230-
}));
11236+
function() {
11237+
$rootScope.test = 'TEST';
11238+
element = $compile('<div sync-test ng-attr-test="{{test}}" test="123"></div>')($rootScope);
11239+
expect(element.attr('test')).toBe('123');
11240+
expect(log.toArray()).toEqual(['TEST', 'TEST']);
11241+
}
11242+
);
1123111243

1123211244

1123311245
it('should provide post-digest value in asynchronous directive link functions when after overridden attribute',
11234-
inject(function(log, $rootScope, $compile) {
11235-
$rootScope.test = 'TEST';
11236-
element = $compile('<div async-test test="123" ng-attr-test="{{test}}"></div>')($rootScope);
11237-
expect(element.attr('test')).toBe('123');
11238-
$rootScope.$digest();
11239-
expect(log.toArray()).toEqual(['TEST', 'TEST']);
11240-
}));
11246+
function() {
11247+
$rootScope.test = 'TEST';
11248+
element = $compile('<div async-test test="123" ng-attr-test="{{test}}"></div>')($rootScope);
11249+
expect(element.attr('test')).toBe('123');
11250+
$rootScope.$digest();
11251+
expect(log.toArray()).toEqual(['TEST', 'TEST']);
11252+
}
11253+
);
1124111254

1124211255
it('should provide post-digest value in asynchronous directive link functions when before overridden attribute',
11243-
inject(function(log, $rootScope, $compile) {
11244-
$rootScope.test = 'TEST';
11245-
element = $compile('<div async-test ng-attr-test="{{test}}" test="123"></div>')($rootScope);
11246-
expect(element.attr('test')).toBe('123');
11247-
$rootScope.$digest();
11248-
expect(log.toArray()).toEqual(['TEST', 'TEST']);
11249-
}));
11256+
function() {
11257+
$rootScope.test = 'TEST';
11258+
element = $compile('<div async-test ng-attr-test="{{test}}" test="123"></div>')($rootScope);
11259+
expect(element.attr('test')).toBe('123');
11260+
$rootScope.$digest();
11261+
expect(log.toArray()).toEqual(['TEST', 'TEST']);
11262+
}
11263+
);
1125011264
});
1125111265

11252-
it('should work with different prefixes', inject(function($compile, $rootScope) {
11266+
it('should work with different prefixes', inject(function() {
1125311267
$rootScope.name = 'Misko';
1125411268
element = $compile('<span ng:attr:test="{{name}}" ng-Attr-test2="{{name}}" ng_Attr_test3="{{name}}"></span>')($rootScope);
1125511269
expect(element.attr('test')).toBeUndefined();
@@ -11261,14 +11275,14 @@ describe('$compile', function() {
1126111275
expect(element.attr('test3')).toBe('Misko');
1126211276
}));
1126311277

11264-
it('should work with the "href" attribute', inject(function($compile, $rootScope) {
11278+
it('should work with the "href" attribute', inject(function() {
1126511279
$rootScope.value = 'test';
1126611280
element = $compile('<a ng-attr-href="test/{{value}}"></a>')($rootScope);
1126711281
$rootScope.$digest();
1126811282
expect(element.attr('href')).toBe('test/test');
1126911283
}));
1127011284

11271-
it('should work if they are prefixed with x- or data- and different prefixes', inject(function($compile, $rootScope) {
11285+
it('should work if they are prefixed with x- or data- and different prefixes', inject(function() {
1127211286
$rootScope.name = 'Misko';
1127311287
element = $compile('<span data-ng-attr-test2="{{name}}" x-ng-attr-test3="{{name}}" data-ng:attr-test4="{{name}}" ' +
1127411288
'x_ng-attr-test5="{{name}}" data:ng-attr-test6="{{name}}"></span>')($rootScope);
@@ -11286,8 +11300,7 @@ describe('$compile', function() {
1128611300
}));
1128711301

1128811302
describe('when an attribute has a dash-separated name', function() {
11289-
11290-
it('should work with different prefixes', inject(function($compile, $rootScope) {
11303+
it('should work with different prefixes', inject(function() {
1129111304
$rootScope.name = 'JamieMason';
1129211305
element = $compile('<span ng:attr:dash-test="{{name}}" ng-Attr-dash-test2="{{name}}" ng_Attr_dash-test3="{{name}}"></span>')($rootScope);
1129311306
expect(element.attr('dash-test')).toBeUndefined();
@@ -11299,7 +11312,7 @@ describe('$compile', function() {
1129911312
expect(element.attr('dash-test3')).toBe('JamieMason');
1130011313
}));
1130111314

11302-
it('should work if they are prefixed with x- or data-', inject(function($compile, $rootScope) {
11315+
it('should work if they are prefixed with x- or data-', inject(function() {
1130311316
$rootScope.name = 'JamieMason';
1130411317
element = $compile('<span data-ng-attr-dash-test2="{{name}}" x-ng-attr-dash-test3="{{name}}" data-ng:attr-dash-test4="{{name}}"></span>')($rootScope);
1130511318
expect(element.attr('dash-test2')).toBeUndefined();
@@ -11328,7 +11341,6 @@ describe('$compile', function() {
1132811341
});
1132911342
});
1133011343

11331-
1133211344
it('should keep attributes ending with -end single-element directives', function() {
1133311345
module(function($compileProvider) {
1133411346
$compileProvider.directive('dashEnder', function(log) {
@@ -11346,7 +11358,6 @@ describe('$compile', function() {
1134611358
});
1134711359
});
1134811360
});
11349-
1135011361
});
1135111362

1135211363

0 commit comments

Comments
 (0)