Skip to content

Commit 9be82d9

Browse files
mheveryIgorMinar
authored andcommitted
refactor($compile): always call attr.$observe
attr.$observe used to call function only if there was interpolation on that attribute. We now call the observation function all the time but we only save the reference to it if interpolation is present.
1 parent 2491319 commit 9be82d9

File tree

4 files changed

+39
-44
lines changed

4 files changed

+39
-44
lines changed

src/ng/compile.js

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -221,9 +221,9 @@ function $CompileProvider($provide) {
221221

222222
this.$get = [
223223
'$injector', '$interpolate', '$exceptionHandler', '$http', '$templateCache', '$parse',
224-
'$controller',
224+
'$controller', '$rootScope',
225225
function($injector, $interpolate, $exceptionHandler, $http, $templateCache, $parse,
226-
$controller) {
226+
$controller, $rootScope) {
227227

228228
var LOCAL_MODE = {
229229
attribute: function(localName, mode, parentScope, scope, attr) {
@@ -268,7 +268,6 @@ function $CompileProvider($provide) {
268268

269269
var Attributes = function(element, attr) {
270270
this.$$element = element;
271-
this.$$observers = {};
272271
this.$attr = attr || {};
273272
};
274273

@@ -286,7 +285,8 @@ function $CompileProvider($provide) {
286285
* @param {string=} attrName Optional none normalized name. Defaults to key.
287286
*/
288287
$set: function(key, value, writeAttr, attrName) {
289-
var booleanKey = getBooleanAttrName(this.$$element[0], key);
288+
var booleanKey = getBooleanAttrName(this.$$element[0], key),
289+
$$observers = this.$$observers;
290290

291291
if (booleanKey) {
292292
this.$$element.prop(key, value);
@@ -314,7 +314,7 @@ function $CompileProvider($provide) {
314314
}
315315

316316
// fire observers
317-
forEach(this.$$observers[key], function(fn) {
317+
$$observers && forEach($$observers[key], function(fn) {
318318
try {
319319
fn(value);
320320
} catch (e) {
@@ -333,10 +333,17 @@ function $CompileProvider($provide) {
333333
* @returns {function(*)} the `fn` Function passed in.
334334
*/
335335
$observe: function(key, fn) {
336-
// keep only observers for interpolated attrs
337-
if (this.$$observers[key]) {
338-
this.$$observers[key].push(fn);
339-
}
336+
var attrs = this,
337+
$$observers = (attrs.$$observers || (attrs.$$observers = {})),
338+
listeners = ($$observers[key] || ($$observers[key] = []));
339+
340+
listeners.push(fn);
341+
$rootScope.$evalAsync(function() {
342+
if (!listeners.$$inter) {
343+
// no one registered attribute interpolation function, so lets call it manually
344+
fn(attrs[key]);
345+
}
346+
});
340347
return fn;
341348
}
342349
};
@@ -990,16 +997,16 @@ function $CompileProvider($provide) {
990997
directives.push({
991998
priority: 100,
992999
compile: valueFn(function(scope, element, attr) {
1000+
var $$observers = (attr.$$observers || (attr.$$observers = {}));
1001+
9931002
if (name === 'class') {
9941003
// we need to interpolate classes again, in the case the element was replaced
9951004
// and therefore the two class attrs got merged - we want to interpolate the result
9961005
interpolateFn = $interpolate(attr[name], true);
9971006
}
9981007

999-
// we define observers array only for interpolated attrs
1000-
// and ignore observers for non interpolated attrs to save some memory
1001-
attr.$$observers[name] = [];
10021008
attr[name] = undefined;
1009+
($$observers[name] || ($$observers[name] = [])).$$inter = true;
10031010
scope.$watch(interpolateFn, function(value) {
10041011
attr.$set(name, value);
10051012
});

src/ng/directive/booleanAttrs.js

Lines changed: 8 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -284,7 +284,6 @@ forEach(BOOLEAN_ATTR, function(propName, attrName) {
284284
priority: 100,
285285
compile: function() {
286286
return function(scope, element, attr) {
287-
attr.$$observers[attrName] = [];
288287
scope.$watch(attr[normalized], function(value) {
289288
attr.$set(attrName, !!value);
290289
});
@@ -301,27 +300,15 @@ forEach(['src', 'href'], function(attrName) {
301300
ngAttributeAliasDirectives[normalized] = function() {
302301
return {
303302
priority: 99, // it needs to run after the attributes are interpolated
304-
compile: function() {
305-
return function(scope, element, attr) {
306-
var value = attr[normalized];
307-
if (value == undefined) {
308-
// undefined value means that the directive is being interpolated
309-
// so just register observer
310-
attr.$$observers[attrName] = [];
311-
attr.$observe(normalized, function(value) {
312-
attr.$set(attrName, value);
303+
link: function(scope, element, attr) {
304+
attr.$observe(normalized, function(value) {
305+
attr.$set(attrName, value);
313306

314-
// on IE, if "ng:src" directive declaration is used and "src" attribute doesn't exist
315-
// then calling element.setAttribute('src', 'foo') doesn't do anything, so we need
316-
// to set the property as well to achieve the desired effect
317-
if (msie) element.prop(attrName, value);
318-
});
319-
} else {
320-
// value present means that no interpolation, so copy to native attribute.
321-
attr.$set(attrName, value);
322-
element.prop(attrName, value);
323-
}
324-
};
307+
// on IE, if "ng:src" directive declaration is used and "src" attribute doesn't exist
308+
// then calling element.setAttribute('src', 'foo') doesn't do anything, so we need
309+
// to set the property as well to achieve the desired effect
310+
if (msie) element.prop(attrName, value);
311+
});
325312
}
326313
};
327314
};

src/ng/directive/input.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1239,7 +1239,6 @@ var ngValueDirective = function() {
12391239
};
12401240
} else {
12411241
return function(scope, elm, attr) {
1242-
attr.$$observers.value = [];
12431242
scope.$watch(attr.ngValue, function(value) {
12441243
attr.$set('value', value, false);
12451244
});

test/ng/compileSpec.js

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1249,15 +1249,15 @@ describe('$compile', function() {
12491249

12501250

12511251
describe('interpolation', function() {
1252-
var observeSpy, attrValueDuringLinking;
1252+
var observeSpy, directiveAttrs;
12531253

12541254
beforeEach(module(function() {
12551255
directive('observer', function() {
12561256
return function(scope, elm, attr) {
1257+
directiveAttrs = attr;
12571258
observeSpy = jasmine.createSpy('$observe attr');
12581259

12591260
expect(attr.$observe('someAttr', observeSpy)).toBe(observeSpy);
1260-
attrValueDuringLinking = attr.someAttr;
12611261
};
12621262
});
12631263
}));
@@ -1295,19 +1295,21 @@ describe('$compile', function() {
12951295

12961296

12971297
it('should set interpolated attrs to undefined', inject(function($rootScope, $compile) {
1298-
attrValueDuringLinking = null;
12991298
$compile('<div some-attr="{{whatever}}" observer></div>')($rootScope);
1300-
expect(attrValueDuringLinking).toBeUndefined();
1299+
expect(directiveAttrs.someAttr).toBeUndefined();
13011300
}));
13021301

13031302

1304-
it('should not call observer of non-interpolated attr', inject(function($rootScope, $compile) {
1305-
$compile('<div some-attr="nonBound" observer></div>')($rootScope);
1306-
expect(attrValueDuringLinking).toBe('nonBound');
1303+
it('should call observer of non-interpolated attr through $evalAsync',
1304+
inject(function($rootScope, $compile) {
1305+
$compile('<div some-attr="nonBound" observer></div>')($rootScope);
1306+
expect(directiveAttrs.someAttr).toBe('nonBound');
13071307

1308-
$rootScope.$digest();
1309-
expect(observeSpy).not.toHaveBeenCalled();
1310-
}));
1308+
expect(observeSpy).not.toHaveBeenCalled();
1309+
$rootScope.$digest();
1310+
expect(observeSpy).toHaveBeenCalled();
1311+
})
1312+
);
13111313

13121314

13131315
it('should delegate exceptions to $exceptionHandler', function() {

0 commit comments

Comments
 (0)