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

Commit 3660fd0

Browse files
petebacondarwinvojtajina
authored andcommitted
feat($compile/ngBind): allow disabling binding info
The compiler and ngBind directives add binding information (`ng-binding` CSS class and `$binding` data property) to elements when they are bound to the scope. This is only to aid testing and debugging for tools such as Protractor and Batarang. In production this is unnecessary and add a performance penalty. This can be now disabled by calling `$compileProvider.debugInfoEnabled(false)` in a module `config` block: ``` someModule.config(['$compileProvider', function($compileProvider) { $compileProvider.debugInfoEnabled(false); }]); ``` In the bench/apps/largetable-bp benchmark this change, with debug info disabled, improved by ~140ms, that is 10%. Measuring the "create" phase, 25 loops, mean time ~1340ms -> ~1200ms. We were storing the whole `interpolationFn` in the `$binding` data on elements but this function was bringing a lot of closure variables with it and so was consuming unwanted amounts of memory. Now we are only storing the parsed interpolation expressions from the binding (i.e. the values of `interpolationFn.expressions`). BREAKING CHANGE: The value of `$binding` data property on an element is always an array now and the expressions do not include the curly braces `{{ ... }}`.
1 parent 4bca4c4 commit 3660fd0

File tree

5 files changed

+84
-50
lines changed

5 files changed

+84
-50
lines changed

src/ng/compile.js

+37-10
Original file line numberDiff line numberDiff line change
@@ -668,6 +668,34 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
668668
}
669669
};
670670

671+
/**
672+
* @ngdoc method
673+
* @name $compileProvider#debugInfoEnabled
674+
*
675+
* @param {boolean=} enabled update the debugInfoEnabled state if provided, otherwise just return the
676+
* current debugInfoEnabled state
677+
* @returns {*} current value if used as getter or itself (chaining) if used as setter
678+
*
679+
* @kind function
680+
*
681+
* @description
682+
* Call this method to enable/disable various debug runtime information in the compiler such as adding
683+
* binding information and a reference to the current scope on to DOM elements.
684+
* If enabled, the compiler will add the following to DOM elements that have been bound to the scope
685+
* * `ng-binding` CSS class
686+
* * `$binding` data property containing an array of the binding expressions
687+
*
688+
* The default value is true.
689+
*/
690+
var debugInfoEnabled = true;
691+
this.debugInfoEnabled = function(enabled) {
692+
if(isDefined(enabled)) {
693+
debugInfoEnabled = enabled;
694+
return this;
695+
}
696+
return debugInfoEnabled;
697+
};
698+
671699
this.$get = [
672700
'$injector', '$interpolate', '$exceptionHandler', '$templateRequest', '$parse',
673701
'$controller', '$rootScope', '$document', '$sce', '$animate', '$$sanitizeUri',
@@ -867,6 +895,13 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
867895
},
868896
NG_ATTR_BINDING = /^ngAttr[A-Z]/;
869897

898+
compile.$$addBindingInfo = debugInfoEnabled ? function $$addBindingInfo(element, binding) {
899+
element
900+
.addClass('ng-binding')
901+
.data('$binding',
902+
(element.data('$binding') || []).concat(binding.expressions || [binding])
903+
);
904+
} : noop;
870905

871906
return compile;
872907

@@ -1947,17 +1982,9 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
19471982
directives.push({
19481983
priority: 0,
19491984
compile: function textInterpolateCompileFn(templateNode) {
1950-
// when transcluding a template that has bindings in the root
1951-
// then we don't have a parent and should do this in the linkFn
1952-
var parent = templateNode.parent(), hasCompileParent = parent.length;
1953-
if (hasCompileParent) safeAddClass(templateNode.parent(), 'ng-binding');
1954-
19551985
return function textInterpolateLinkFn(scope, node) {
1956-
var parent = node.parent(),
1957-
bindings = parent.data('$binding') || [];
1958-
bindings.push(interpolateFn);
1959-
parent.data('$binding', bindings);
1960-
if (!hasCompileParent) safeAddClass(parent, 'ng-binding');
1986+
var parent = node.parent();
1987+
compile.$$addBindingInfo(parent, interpolateFn);
19611988
scope.$watch(interpolateFn, function interpolateFnWatchAction(value) {
19621989
node[0].nodeValue = value;
19631990
});

src/ng/directive/ngBind.js

+22-25
Original file line numberDiff line numberDiff line change
@@ -51,23 +51,22 @@
5151
</file>
5252
</example>
5353
*/
54-
var ngBindDirective = ngDirective({
55-
compile: function ngBindCompile(templateElement) {
56-
templateElement.addClass('ng-binding');
57-
58-
return function ngBindLink(scope, element, attr) {
59-
element.data('$binding', attr.ngBind);
60-
element = element[0];
61-
62-
scope.$watch(attr.ngBind, function ngBindWatchAction(value) {
63-
// We are purposefully using == here rather than === because we want to
64-
// catch when value is "null or undefined"
65-
// jshint -W041
66-
element.textContent = (value == undefined ? '' : value);
67-
});
68-
};
69-
}
70-
});
54+
var ngBindDirective = ['$compile', function($compile) {
55+
return {
56+
restrict: 'AC',
57+
compile: function(templateElement) {
58+
return function (scope, element, attr) {
59+
$compile.$$addBindingInfo(element, attr.ngBind);
60+
scope.$watch(attr.ngBind, function ngBindWatchAction(value) {
61+
// We are purposefully using == here rather than === because we want to
62+
// catch when value is "null or undefined"
63+
// jshint -W041
64+
element.text(value == undefined ? '' : value);
65+
});
66+
};
67+
}
68+
};
69+
}];
7170

7271

7372
/**
@@ -121,11 +120,10 @@ var ngBindDirective = ngDirective({
121120
</file>
122121
</example>
123122
*/
124-
var ngBindTemplateDirective = ['$interpolate', function($interpolate) {
123+
var ngBindTemplateDirective = ['$interpolate', '$compile', function($interpolate, $compile) {
125124
return function(scope, element, attr) {
126-
// TODO: move this to scenario runner
127125
var interpolateFn = $interpolate(element.attr(attr.$attr.ngBindTemplate));
128-
element.addClass('ng-binding').data('$binding', interpolateFn);
126+
$compile.$$addBindingInfo(element, interpolateFn);
129127
attr.$observe('ngBindTemplate', function(value) {
130128
element.text(value);
131129
});
@@ -178,14 +176,13 @@ var ngBindTemplateDirective = ['$interpolate', function($interpolate) {
178176
</file>
179177
</example>
180178
*/
181-
var ngBindHtmlDirective = ['$sce', '$parse', function($sce, $parse) {
179+
var ngBindHtmlDirective = ['$sce', '$parse', '$compile', function($sce, $parse, $compile) {
182180
return {
183181
restrict: 'A',
184-
compile: function ngBindCompile(tElement, tAttrs) {
185-
tElement.addClass('ng-binding');
182+
compile: function (tElement, tAttrs) {
186183

187-
return function ngBindLink(scope, element, attr) {
188-
element.data('$binding', attr.ngBindHtml);
184+
return function (scope, element, attr) {
185+
$compile.$$addBindingInfo(element, attr.ngBindHtml);
189186
var ngBindHtmlGetter = $parse(attr.ngBindHtml);
190187
var ngBindHtmlWatch = $parse(attr.ngBindHtml, function getStringValue(value) {
191188
return (value || '').toString();

src/ngScenario/Scenario.js

-3
Original file line numberDiff line numberDiff line change
@@ -304,9 +304,6 @@ _jQuery.fn.bindings = function(windowJquery, bindExp) {
304304
var element = windowJquery(this),
305305
bindings;
306306
if (bindings = element.data('$binding')) {
307-
if (!angular.isArray(bindings)) {
308-
bindings = [bindings];
309-
}
310307
for(var expressions = [], binding, j=0, jj=bindings.length; j<jj; j++) {
311308
binding = bindings[j];
312309

test/ng/compileSpec.js

+25-5
Original file line numberDiff line numberDiff line change
@@ -2491,14 +2491,34 @@ describe('$compile', function() {
24912491
}));
24922492
});
24932493

2494-
it('should decorate the binding with ng-binding and interpolation function', inject(
2495-
function($compile, $rootScope) {
2494+
describe('decorating with binding info', function() {
2495+
2496+
it('should not occur if `debugInfoEnabled` is false', function() {
2497+
module(function($compileProvider) {
2498+
$compileProvider.debugInfoEnabled(false);
2499+
});
2500+
2501+
inject(function($compile, $rootScope) {
24962502
element = $compile('<div>{{1+2}}</div>')($rootScope);
2497-
expect(element.hasClass('ng-binding')).toBe(true);
2498-
expect(element.data('$binding')[0].exp).toEqual('{{1+2}}');
2499-
}));
2503+
expect(element.hasClass('ng-binding')).toBe(false);
2504+
expect(element.data('$binding')).toBeUndefined();
2505+
});
2506+
});
25002507

25012508

2509+
it('should occur if `debugInfoEnabled` is true', function() {
2510+
module(function($compileProvider) {
2511+
$compileProvider.debugInfoEnabled(true);
2512+
});
2513+
2514+
inject(function($compile, $rootScope) {
2515+
element = $compile('<div>{{1+2}}</div>')($rootScope);
2516+
expect(element.hasClass('ng-binding')).toBe(true);
2517+
expect(element.data('$binding')).toEqual(['1+2']);
2518+
});
2519+
});
2520+
});
2521+
25022522
it('should observe interpolated attrs', inject(function($rootScope, $compile) {
25032523
$compile('<div some-attr="{{value}}" observer></div>')($rootScope);
25042524

test/ng/directive/ngBindSpec.js

-7
Original file line numberDiff line numberDiff line change
@@ -122,13 +122,6 @@ describe('ngBind*', function() {
122122

123123
describe('ngBindHtml', function() {
124124

125-
it('should add ng-binding class to the element in compile phase', inject(function($compile) {
126-
var element = jqLite('<div ng-bind-html="myHtml"></div>');
127-
$compile(element);
128-
expect(element.hasClass('ng-binding')).toBe(true);
129-
}));
130-
131-
132125
describe('SCE disabled', function() {
133126
beforeEach(function() {
134127
module(function($sceProvider) { $sceProvider.enabled(false); });

0 commit comments

Comments
 (0)