diff --git a/docs/content/guide/$location.ngdoc b/docs/content/guide/$location.ngdoc
index 81a7d85ad299..f7a1557a90f8 100644
--- a/docs/content/guide/$location.ngdoc
+++ b/docs/content/guide/$location.ngdoc
@@ -469,12 +469,12 @@ In these examples we use ``
it("should show fake browser info on load", function(){
expect(addressBar.getAttribute('value')).toBe(url);
- expect(element(by.binding('$location.protocol')).getText()).toBe('http');
- expect(element(by.binding('$location.host')).getText()).toBe('www.example.com');
- expect(element(by.binding('$location.port')).getText()).toBe('80');
- expect(element(by.binding('$location.path')).getText()).toBe('/path');
- expect(element(by.binding('$location.search')).getText()).toBe('{"a":"b"}');
- expect(element(by.binding('$location.hash')).getText()).toBe('h');
+ expect(element(by.binding('$location.protocol()')).getText()).toBe('http');
+ expect(element(by.binding('$location.host()')).getText()).toBe('www.example.com');
+ expect(element(by.binding('$location.port()')).getText()).toBe('80');
+ expect(element(by.binding('$location.path()')).getText()).toBe('/path');
+ expect(element(by.binding('$location.search()')).getText()).toBe('{"a":"b"}');
+ expect(element(by.binding('$location.hash()')).getText()).toBe('h');
});
@@ -485,24 +485,24 @@ In these examples we use ``
expect(addressBar.getAttribute('value')).toBe("http://www.example.com/base/first?a=b");
- expect(element(by.binding('$location.protocol')).getText()).toBe('http');
- expect(element(by.binding('$location.host')).getText()).toBe('www.example.com');
- expect(element(by.binding('$location.port')).getText()).toBe('80');
- expect(element(by.binding('$location.path')).getText()).toBe('/first');
- expect(element(by.binding('$location.search')).getText()).toBe('{"a":"b"}');
- expect(element(by.binding('$location.hash')).getText()).toBe('');
+ expect(element(by.binding('$location.protocol()')).getText()).toBe('http');
+ expect(element(by.binding('$location.host()')).getText()).toBe('www.example.com');
+ expect(element(by.binding('$location.port()')).getText()).toBe('80');
+ expect(element(by.binding('$location.path()')).getText()).toBe('/first');
+ expect(element(by.binding('$location.search()')).getText()).toBe('{"a":"b"}');
+ expect(element(by.binding('$location.hash()')).getText()).toBe('');
navigation.get(1).click();
expect(addressBar.getAttribute('value')).toBe("http://www.example.com/base/sec/ond?flag#hash");
- expect(element(by.binding('$location.protocol')).getText()).toBe('http');
- expect(element(by.binding('$location.host')).getText()).toBe('www.example.com');
- expect(element(by.binding('$location.port')).getText()).toBe('80');
- expect(element(by.binding('$location.path')).getText()).toBe('/sec/ond');
- expect(element(by.binding('$location.search')).getText()).toBe('{"flag":true}');
- expect(element(by.binding('$location.hash')).getText()).toBe('hash');
+ expect(element(by.binding('$location.protocol()')).getText()).toBe('http');
+ expect(element(by.binding('$location.host()')).getText()).toBe('www.example.com');
+ expect(element(by.binding('$location.port()')).getText()).toBe('80');
+ expect(element(by.binding('$location.path()')).getText()).toBe('/sec/ond');
+ expect(element(by.binding('$location.search()')).getText()).toBe('{"flag":true}');
+ expect(element(by.binding('$location.hash()')).getText()).toBe('hash');
});
@@ -621,12 +621,12 @@ In these examples we use ``
it("should show fake browser info on load", function(){
expect(addressBar.getAttribute('value')).toBe(url);
- expect(element(by.binding('$location.protocol')).getText()).toBe('http');
- expect(element(by.binding('$location.host')).getText()).toBe('www.example.com');
- expect(element(by.binding('$location.port')).getText()).toBe('80');
- expect(element(by.binding('$location.path')).getText()).toBe('/path');
- expect(element(by.binding('$location.search')).getText()).toBe('{"a":"b"}');
- expect(element(by.binding('$location.hash')).getText()).toBe('h');
+ expect(element(by.binding('$location.protocol()')).getText()).toBe('http');
+ expect(element(by.binding('$location.host()')).getText()).toBe('www.example.com');
+ expect(element(by.binding('$location.port()')).getText()).toBe('80');
+ expect(element(by.binding('$location.path()')).getText()).toBe('/path');
+ expect(element(by.binding('$location.search()')).getText()).toBe('{"a":"b"}');
+ expect(element(by.binding('$location.hash()')).getText()).toBe('h');
});
@@ -637,24 +637,24 @@ In these examples we use ``
expect(addressBar.getAttribute('value')).toBe("http://www.example.com/base/index.html#!/first?a=b");
- expect(element(by.binding('$location.protocol')).getText()).toBe('http');
- expect(element(by.binding('$location.host')).getText()).toBe('www.example.com');
- expect(element(by.binding('$location.port')).getText()).toBe('80');
- expect(element(by.binding('$location.path')).getText()).toBe('/first');
- expect(element(by.binding('$location.search')).getText()).toBe('{"a":"b"}');
- expect(element(by.binding('$location.hash')).getText()).toBe('');
+ expect(element(by.binding('$location.protocol()')).getText()).toBe('http');
+ expect(element(by.binding('$location.host()')).getText()).toBe('www.example.com');
+ expect(element(by.binding('$location.port()')).getText()).toBe('80');
+ expect(element(by.binding('$location.path()')).getText()).toBe('/first');
+ expect(element(by.binding('$location.search()')).getText()).toBe('{"a":"b"}');
+ expect(element(by.binding('$location.hash()')).getText()).toBe('');
navigation.get(1).click();
expect(addressBar.getAttribute('value')).toBe("http://www.example.com/base/index.html#!/sec/ond?flag#hash");
- expect(element(by.binding('$location.protocol')).getText()).toBe('http');
- expect(element(by.binding('$location.host')).getText()).toBe('www.example.com');
- expect(element(by.binding('$location.port')).getText()).toBe('80');
- expect(element(by.binding('$location.path')).getText()).toBe('/sec/ond');
- expect(element(by.binding('$location.search')).getText()).toBe('{"flag":true}');
- expect(element(by.binding('$location.hash')).getText()).toBe('hash');
+ expect(element(by.binding('$location.protocol()')).getText()).toBe('http');
+ expect(element(by.binding('$location.host()')).getText()).toBe('www.example.com');
+ expect(element(by.binding('$location.port()')).getText()).toBe('80');
+ expect(element(by.binding('$location.path()')).getText()).toBe('/sec/ond');
+ expect(element(by.binding('$location.search()')).getText()).toBe('{"flag":true}');
+ expect(element(by.binding('$location.hash()')).getText()).toBe('hash');
});
diff --git a/docs/content/guide/index.ngdoc b/docs/content/guide/index.ngdoc
index 0ca0abdcb54d..93bc7b42d062 100644
--- a/docs/content/guide/index.ngdoc
+++ b/docs/content/guide/index.ngdoc
@@ -81,6 +81,7 @@ This is a short list of libraries with specific support and documentation for wo
### General
+* **Docs Page:** {@link guide/production Running an AngularJS App in Production}
* **Javascript minification: **[Background](http://thegreenpizza.github.io/2013/05/25/building-minification-safe-angular.js-applications/), [ng-annotate automation tool](https://github.com/olov/ng-annotate)
* **Analytics and Logging:** [Angularyitcs (Google Analytics)](http://ngmodules.org/modules/angularytics), [Angulartics (Analytics)](https://github.com/luisfarzati/angulartics), [Logging Client-Side Errors](http://www.bennadel.com/blog/2542-Logging-Client-Side-Errors-With-AngularJS-And-Stacktrace-js.htm)
* **SEO:** [By hand](http://www.yearofmoo.com/2012/11/angularjs-and-seo.html), [prerender.io](http://prerender.io/), [Brombone](http://www.brombone.com/), [SEO.js](http://getseojs.com/), [SEO4Ajax](http://www.seo4ajax.com/)
diff --git a/docs/content/guide/module.ngdoc b/docs/content/guide/module.ngdoc
index 5315da44ba91..1ef968347a05 100644
--- a/docs/content/guide/module.ngdoc
+++ b/docs/content/guide/module.ngdoc
@@ -50,7 +50,7 @@ I'm in a hurry. How do I get a Hello World module working?
it('should add Hello to the name', function() {
- expect(element(by.binding("{{ 'World' | greet }}")).getText()).toEqual('Hello, World!');
+ expect(element(by.binding(" 'World' | greet ")).getText()).toEqual('Hello, World!');
});
@@ -128,7 +128,7 @@ The above is a suggestion. Tailor it to your needs.
it('should add Hello to the name', function() {
- expect(element(by.binding("{{ greeting }}")).getText()).toEqual('Bonjour World!');
+ expect(element(by.binding(" greeting ")).getText()).toEqual('Bonjour World!');
});
diff --git a/docs/content/guide/production.ngdoc b/docs/content/guide/production.ngdoc
new file mode 100644
index 000000000000..996e0486f5fc
--- /dev/null
+++ b/docs/content/guide/production.ngdoc
@@ -0,0 +1,42 @@
+@ngdoc overview
+@name Running in Production
+@description
+
+# Running an AngularJS App in Production
+
+There are a few things you might consider when running your AngularJS application in production.
+
+
+## Disabling Debug Data
+
+By default AngularJS attaches information about scopes to DOM nodes, and adds CSS classes
+to data-bound elements. The information that is not included is:
+
+As a result of `ngBind`, `ngBindHtml` or `{{...}}` interpolations, binding data and CSS class
+`ng-class` is attached to the corresponding element.
+
+Where the compiler has created a new scope, the scope and either `ng-scope` or `ng-isolated-scope`
+CSS class are attached to the corresponding element. These scope references can then be accessed via
+`element.scope()` and `element.isolateScope()`.
+
+Tools like [Protractor](github.com/angular/protractor) and
+[Batarang](https://github.com/angular/angularjs-batarang) need this information to run,
+but you can disable this in production for a significant performance boost with:
+
+```js
+myApp.config(['$compileProvider', function ($compileProvider) {
+ $compileProvider.debugInfoEnabled(false);
+}]);
+```
+
+If you wish to debug an application with this information then you should open up a debug
+console in the browser then call this method directly in this console:
+
+```js
+angular.reloadWithDebugInfo();
+```
+
+The page should reload and the debug information should now be available.
+
+For more see the docs pages on {@link ng.$compileProvider#debugInfoEnabled `$compileProvider`}
+and {@link ng/function/angular.reloadWithDebugInfo `angular.reloadWithDebugInfo`}.
diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json
index d057b17689ef..cc3cf7043cd4 100644
--- a/npm-shrinkwrap.json
+++ b/npm-shrinkwrap.json
@@ -3230,7 +3230,7 @@
}
},
"protractor": {
- "version": "1.0.0",
+ "version": "1.1.1",
"dependencies": {
"request": {
"version": "2.36.0",
@@ -3254,7 +3254,7 @@
"version": "0.12.1",
"dependencies": {
"punycode": {
- "version": "1.3.0"
+ "version": "1.3.1"
}
}
},
@@ -3361,6 +3361,9 @@
}
}
},
+ "q": {
+ "version": "1.0.0"
+ },
"lodash": {
"version": "2.4.1"
},
diff --git a/scripts/clean-shrinkwrap.js b/scripts/clean-shrinkwrap.js
index 66326f4f2fca..c46fe295da6b 100755
--- a/scripts/clean-shrinkwrap.js
+++ b/scripts/clean-shrinkwrap.js
@@ -23,9 +23,6 @@ function cleanModule(module, name) {
if (name === 'chokidar') {
if (module.version === '0.8.1') {
delete module.dependencies;
- } else if ( module.version !== '0.8.2') {
- throw new Error("Unfamiliar chokidar version (v" + module.version +
- ") , please check status of https://github.com/paulmillr/chokidar/pull/106");
}
}
diff --git a/src/.jshintrc b/src/.jshintrc
index 87dd0638cf94..6b7190b12ff8 100644
--- a/src/.jshintrc
+++ b/src/.jshintrc
@@ -88,6 +88,7 @@
"getBlockNodes": false,
"createMap": false,
"VALIDITY_STATE_PROPERTY": false,
+ "reloadWithDebugInfo": false,
"skipDestroyOnNextJQueryCleanData": true,
diff --git a/src/Angular.js b/src/Angular.js
index 189e2e45378b..d3c80e1cc841 100644
--- a/src/Angular.js
+++ b/src/Angular.js
@@ -1402,6 +1402,14 @@ function bootstrap(element, modules, config) {
modules.unshift(['$provide', function($provide) {
$provide.value('$rootElement', element);
}]);
+
+ if (config.debugInfoEnabled) {
+ // Pushing so that this overrides `debugInfoEnabled` setting defined in user's `modules`.
+ modules.push(['$compileProvider', function($compileProvider) {
+ $compileProvider.debugInfoEnabled(true);
+ }]);
+ }
+
modules.unshift('ng');
var injector = createInjector(modules, config.strictDi);
injector.invoke(['$rootScope', '$rootElement', '$compile', '$injector',
@@ -1415,8 +1423,14 @@ function bootstrap(element, modules, config) {
return injector;
};
+ var NG_ENABLE_DEBUG_INFO = /^NG_ENABLE_DEBUG_INFO!/;
var NG_DEFER_BOOTSTRAP = /^NG_DEFER_BOOTSTRAP!/;
+ if (window && NG_ENABLE_DEBUG_INFO.test(window.name)) {
+ config.debugInfoEnabled = true;
+ window.name = window.name.replace(NG_ENABLE_DEBUG_INFO, '');
+ }
+
if (window && !NG_DEFER_BOOTSTRAP.test(window.name)) {
return doBootstrap();
}
@@ -1430,6 +1444,21 @@ function bootstrap(element, modules, config) {
};
}
+/**
+ * @ngdoc function
+ * @name angular.reloadWithDebugInfo
+ * @module ng
+ * @description
+ * Use this function to reload the current application with debug information turned on.
+ * This takes precedence over a call to `$compileProvider.debugInfoEnabled(false)`.
+ *
+ * See {@link ng.$compileProvider#debugInfoEnabled} for more.
+ */
+function reloadWithDebugInfo() {
+ window.name = 'NG_ENABLE_DEBUG_INFO!' + window.name;
+ window.location.reload();
+}
+
var SNAKE_CASE_REGEXP = /[A-Z]/g;
function snake_case(name, separator) {
separator = separator || '_';
diff --git a/src/AngularPublic.js b/src/AngularPublic.js
index e859a0da7577..a0a61b727b39 100644
--- a/src/AngularPublic.js
+++ b/src/AngularPublic.js
@@ -136,7 +136,8 @@ function publishExternalAPI(angular){
'uppercase': uppercase,
'callbacks': {counter: 0},
'$$minErr': minErr,
- '$$csp': csp
+ '$$csp': csp,
+ 'reloadWithDebugInfo': reloadWithDebugInfo
});
angularModule = setupModuleLoader(window);
diff --git a/src/ng/compile.js b/src/ng/compile.js
index ac0eab395690..140359a89f21 100644
--- a/src/ng/compile.js
+++ b/src/ng/compile.js
@@ -668,6 +668,37 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
}
};
+ /**
+ * @ngdoc method
+ * @name $compileProvider#debugInfoEnabled
+ *
+ * @param {boolean=} enabled update the debugInfoEnabled state if provided, otherwise just return the
+ * current debugInfoEnabled state
+ * @returns {*} current value if used as getter or itself (chaining) if used as setter
+ *
+ * @kind function
+ *
+ * @description
+ * Call this method to enable/disable various debug runtime information in the compiler such as adding
+ * binding information and a reference to the current scope on to DOM elements.
+ * If enabled, the compiler will add the following to DOM elements that have been bound to the scope
+ * * `ng-binding` CSS class
+ * * `$binding` data property containing an array of the binding expressions
+ *
+ * You may want to use this in production for a significant performance boost. See
+ * {@link guide/production#disabling-debug-data Disabling Debug Data} for more.
+ *
+ * The default value is true.
+ */
+ var debugInfoEnabled = true;
+ this.debugInfoEnabled = function(enabled) {
+ if(isDefined(enabled)) {
+ debugInfoEnabled = enabled;
+ return this;
+ }
+ return debugInfoEnabled;
+ };
+
this.$get = [
'$injector', '$interpolate', '$exceptionHandler', '$http', '$templateCache', '$parse',
'$controller', '$rootScope', '$document', '$sce', '$animate', '$$sanitizeUri',
@@ -858,6 +889,17 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
}
};
+
+ function safeAddClass($element, className) {
+ try {
+ $element.addClass(className);
+ } catch(e) {
+ // ignore, since it means that we are trying to set class on
+ // SVG element, where class name is read-only.
+ }
+ }
+
+
var startSymbol = $interpolate.startSymbol(),
endSymbol = $interpolate.endSymbol(),
denormalizeTemplate = (startSymbol == '{{' || endSymbol == '}}')
@@ -867,6 +909,30 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
},
NG_ATTR_BINDING = /^ngAttr[A-Z]/;
+ compile.$$addBindingInfo = debugInfoEnabled ? function $$addBindingInfo($element, binding) {
+ var bindings = $element.data('$binding') || [];
+
+ if (isArray(binding)) {
+ bindings = bindings.concat(binding);
+ } else {
+ bindings.push(binding);
+ }
+
+ $element.data('$binding', bindings);
+ } : noop;
+
+ compile.$$addBindingClass = debugInfoEnabled ? function $$addBindingClass($element) {
+ safeAddClass($element, 'ng-binding');
+ } : noop;
+
+ compile.$$addScopeInfo = debugInfoEnabled ? function $$addScopeInfo($element, scope, isolated, noTemplate) {
+ var dataName = isolated ? (noTemplate ? '$isolateScopeNoTemplate' : '$isolateScope') : '$scope';
+ $element.data(dataName, scope);
+ } : noop;
+
+ compile.$$addScopeClass = debugInfoEnabled ? function $$addScopeClass($element, isolated) {
+ safeAddClass($element, isolated ? 'ng-isolate-scope' : 'ng-scope');
+ } : noop;
return compile;
@@ -889,9 +955,11 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
var compositeLinkFn =
compileNodes($compileNodes, transcludeFn, $compileNodes,
maxPriority, ignoreDirective, previousCompileContext);
- safeAddClass($compileNodes, 'ng-scope');
- var namespace = null;
+
+ compile.$$addScopeClass($compileNodes);
+
return function publicLinkFn(scope, cloneConnectFn, transcludeControllers, parentBoundTranscludeFn, futureParentElement){
+ var namespace = null;
assertArg(scope, 'scope');
if (!namespace) {
namespace = detectNamespaceForChildElements(futureParentElement);
@@ -914,7 +982,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
}
}
- $linkNode.data('$scope', scope);
+ compile.$$addScopeInfo($linkNode, scope);
if (cloneConnectFn) cloneConnectFn($linkNode, scope);
if (compositeLinkFn) compositeLinkFn(scope, $linkNode, $linkNode, parentBoundTranscludeFn);
@@ -932,15 +1000,6 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
}
}
- function safeAddClass($element, className) {
- try {
- $element.addClass(className);
- } catch(e) {
- // ignore, since it means that we are trying to set class on
- // SVG element, where class name is read-only.
- }
- }
-
/**
* Compile function matches each node in nodeList against the directives. Once all directives
* for a particular node are collected their compile functions are executed. The compile
@@ -974,7 +1033,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
: null;
if (nodeLinkFn && nodeLinkFn.scope) {
- safeAddClass(attrs.$$element, 'ng-scope');
+ compile.$$addScopeClass(attrs.$$element);
}
childLinkFn = (nodeLinkFn && nodeLinkFn.terminal ||
@@ -1027,7 +1086,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
if (nodeLinkFn) {
if (nodeLinkFn.scope) {
childScope = scope.$new();
- jqLite.data(node, '$scope', childScope);
+ compile.$$addScopeInfo(jqLite(node), childScope);
} else {
childScope = scope;
}
@@ -1528,16 +1587,9 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
isolateScope = scope.$new(true);
- if (templateDirective && (templateDirective === newIsolateScopeDirective ||
- templateDirective === newIsolateScopeDirective.$$originalDirective)) {
- $element.data('$isolateScope', isolateScope);
- } else {
- $element.data('$isolateScopeNoTemplate', isolateScope);
- }
-
-
-
- safeAddClass($element, 'ng-isolate-scope');
+ compile.$$addScopeInfo($element, isolateScope, true, !(templateDirective && (templateDirective === newIsolateScopeDirective ||
+ templateDirective === newIsolateScopeDirective.$$originalDirective)));
+ compile.$$addScopeClass($element, true);
forEach(newIsolateScopeDirective.scope, function(definition, scopeName) {
var match = definition.match(LOCAL_REGEXP) || [],
@@ -1950,17 +2002,17 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
directives.push({
priority: 0,
compile: function textInterpolateCompileFn(templateNode) {
- // when transcluding a template that has bindings in the root
- // then we don't have a parent and should do this in the linkFn
- var parent = templateNode.parent(), hasCompileParent = parent.length;
- if (hasCompileParent) safeAddClass(templateNode.parent(), 'ng-binding');
+ var templateNodeParent = templateNode.parent(),
+ hasCompileParent = !!templateNodeParent.length;
+
+ // When transcluding a template that has bindings in the root
+ // we don't have a parent and thus need to add the class during linking fn.
+ if (hasCompileParent) compile.$$addBindingClass(templateNodeParent);
return function textInterpolateLinkFn(scope, node) {
- var parent = node.parent(),
- bindings = parent.data('$binding') || [];
- bindings.push(interpolateFn);
- parent.data('$binding', bindings);
- if (!hasCompileParent) safeAddClass(parent, 'ng-binding');
+ var parent = node.parent();
+ if (!hasCompileParent) compile.$$addBindingClass(parent);
+ compile.$$addBindingInfo(parent, interpolateFn.expressions);
scope.$watch(interpolateFn, function interpolateFnWatchAction(value) {
node[0].nodeValue = value;
});
diff --git a/src/ng/directive/input.js b/src/ng/directive/input.js
index 911ec30f0cd7..068079fb1e51 100644
--- a/src/ng/directive/input.js
+++ b/src/ng/directive/input.js
@@ -814,7 +814,7 @@ var inputType = {
it('should change state', function() {
- var color = element(by.binding('color'));
+ var color = element(by.binding('color | json'));
expect(color.getText()).toContain('blue');
@@ -1313,7 +1313,7 @@ function checkboxInputType(scope, element, attr, ctrl, $sniffer, $browser, $filt
- var user = element(by.binding('{{user}}'));
+ var user = element(by.binding('user'));
var userNameValid = element(by.binding('myForm.userName.$valid'));
var lastNameValid = element(by.binding('myForm.lastName.$valid'));
var lastNameError = element(by.binding('myForm.lastName.$error'));
@@ -2542,7 +2542,7 @@ var minlengthDirective = function() {
*
*
* var listInput = element(by.model('names'));
- * var names = element(by.binding('{{names}}'));
+ * var names = element(by.binding('names'));
* var valid = element(by.binding('myForm.namesInput.$valid'));
* var error = element(by.css('span.error'));
*
@@ -2572,7 +2572,7 @@ var minlengthDirective = function() {
*
* it("should split the text by newlines", function() {
* var listInput = element(by.model('list'));
- * var output = element(by.binding('{{ list | json }}'));
+ * var output = element(by.binding(' list | json '));
* listInput.sendKeys('abc\ndef\nghi');
* expect(output.getText()).toContain('[\n "abc",\n "def",\n "ghi"\n]');
* });
diff --git a/src/ng/directive/ngBind.js b/src/ng/directive/ngBind.js
index d4dd81fc9121..e1891a1a960a 100644
--- a/src/ng/directive/ngBind.js
+++ b/src/ng/directive/ngBind.js
@@ -51,23 +51,23 @@
*/
-var ngBindDirective = ngDirective({
- compile: function ngBindCompile(templateElement) {
- templateElement.addClass('ng-binding');
-
- return function ngBindLink(scope, element, attr) {
- element.data('$binding', attr.ngBind);
- element = element[0];
-
- scope.$watch(attr.ngBind, function ngBindWatchAction(value) {
- // We are purposefully using == here rather than === because we want to
- // catch when value is "null or undefined"
- // jshint -W041
- element.textContent = (value == undefined ? '' : value);
- });
- };
- }
-});
+var ngBindDirective = ['$compile', function($compile) {
+ return {
+ restrict: 'AC',
+ compile: function ngBindCompile(templateElement) {
+ $compile.$$addBindingClass(templateElement);
+ return function ngBindLink(scope, element, attr) {
+ $compile.$$addBindingInfo(element, attr.ngBind);
+ scope.$watch(attr.ngBind, function ngBindWatchAction(value) {
+ // We are purposefully using == here rather than === because we want to
+ // catch when value is "null or undefined"
+ // jshint -W041
+ element.text(value == undefined ? '' : value);
+ });
+ };
+ }
+ };
+}];
/**
@@ -121,14 +121,18 @@ var ngBindDirective = ngDirective({
*/
-var ngBindTemplateDirective = ['$interpolate', function($interpolate) {
- return function(scope, element, attr) {
- // TODO: move this to scenario runner
- var interpolateFn = $interpolate(element.attr(attr.$attr.ngBindTemplate));
- element.addClass('ng-binding').data('$binding', interpolateFn);
- attr.$observe('ngBindTemplate', function(value) {
- element.text(value);
- });
+var ngBindTemplateDirective = ['$interpolate', '$compile', function($interpolate, $compile) {
+ return {
+ compile: function ngBindTemplateCompile(templateElement) {
+ $compile.$$addBindingClass(templateElement);
+ return function ngBindTemplateLink(scope, element, attr) {
+ var interpolateFn = $interpolate(element.attr(attr.$attr.ngBindTemplate));
+ $compile.$$addBindingInfo(element, interpolateFn.expressions);
+ attr.$observe('ngBindTemplate', function(value) {
+ element.text(value);
+ });
+ };
+ }
};
}];
@@ -178,14 +182,14 @@ var ngBindTemplateDirective = ['$interpolate', function($interpolate) {
*/
-var ngBindHtmlDirective = ['$sce', '$parse', function($sce, $parse) {
+var ngBindHtmlDirective = ['$sce', '$parse', '$compile', function($sce, $parse, $compile) {
return {
restrict: 'A',
- compile: function ngBindCompile(tElement, tAttrs) {
- tElement.addClass('ng-binding');
+ compile: function ngBindHtmlCompile(tElement, tAttrs) {
+ $compile.$$addBindingClass(tElement);
- return function ngBindLink(scope, element, attr) {
- element.data('$binding', attr.ngBindHtml);
+ return function ngBindHtmlLink(scope, element, attr) {
+ $compile.$$addBindingInfo(element, attr.ngBindHtml);
var ngBindHtmlGetter = $parse(attr.ngBindHtml);
var ngBindHtmlWatch = $parse(attr.ngBindHtml, function getStringValue(value) {
return (value || '').toString();
diff --git a/src/ng/directive/select.js b/src/ng/directive/select.js
index 8bb93c56ceec..285791e64727 100644
--- a/src/ng/directive/select.js
+++ b/src/ng/directive/select.js
@@ -115,7 +115,7 @@ var ngOptionsMinErr = minErr('ngOptions');
Select bogus.
- Currently selected: {{ {selected_color:myColor} }}
+ Currently selected: {{ {selected_color:myColor} }}
@@ -123,13 +123,13 @@ var ngOptionsMinErr = minErr('ngOptions');
it('should check ng-options', function() {
- expect(element(by.binding('{selected_color:myColor}')).getText()).toMatch('red');
+ expect(element(by.binding(' {selected_color:myColor} ')).getText()).toMatch('red');
element.all(by.model('myColor')).first().click();
element.all(by.css('select[ng-model="myColor"] option')).first().click();
- expect(element(by.binding('{selected_color:myColor}')).getText()).toMatch('black');
+ expect(element(by.binding(' {selected_color:myColor} ')).getText()).toMatch('black');
element(by.css('.nullable select[ng-model="myColor"]')).click();
element.all(by.css('.nullable select[ng-model="myColor"] option')).first().click();
- expect(element(by.binding('{selected_color:myColor}')).getText()).toMatch('null');
+ expect(element(by.binding(' {selected_color:myColor} ')).getText()).toMatch('null');
});
diff --git a/src/ng/filter/filters.js b/src/ng/filter/filters.js
index 65ea0e1ab0f4..0b69d7b4ba07 100644
--- a/src/ng/filter/filters.js
+++ b/src/ng/filter/filters.js
@@ -490,7 +490,7 @@ function dateFilter($locale) {
it('should jsonify filtered objects', function() {
- expect(element(by.binding("{'name':'value'}")).getText()).toMatch(/\{\n "name": ?"value"\n}/);
+ expect(element(by.binding(" {'name':'value'} | json ")).getText()).toMatch(/\{\n "name": ?"value"\n}/);
});
diff --git a/src/ng/filter/limitTo.js b/src/ng/filter/limitTo.js
index 52abd260b9a2..6f01aa9c1f86 100644
--- a/src/ng/filter/limitTo.js
+++ b/src/ng/filter/limitTo.js
@@ -40,8 +40,8 @@
var numLimitInput = element(by.model('numLimit'));
var letterLimitInput = element(by.model('letterLimit'));
- var limitedNumbers = element(by.binding('numbers | limitTo:numLimit'));
- var limitedLetters = element(by.binding('letters | limitTo:letterLimit'));
+ var limitedNumbers = element(by.binding(' numbers | limitTo:numLimit '));
+ var limitedLetters = element(by.binding(' letters | limitTo:letterLimit '));
it('should limit the number array to first three items', function() {
expect(numLimitInput.getAttribute('value')).toBe('3');
diff --git a/src/ngScenario/Scenario.js b/src/ngScenario/Scenario.js
index 6cf69d84ce79..7ee8030d404c 100644
--- a/src/ngScenario/Scenario.js
+++ b/src/ngScenario/Scenario.js
@@ -304,9 +304,6 @@ _jQuery.fn.bindings = function(windowJquery, bindExp) {
var element = windowJquery(this),
bindings;
if (bindings = element.data('$binding')) {
- if (!angular.isArray(bindings)) {
- bindings = [bindings];
- }
for(var expressions = [], binding, j=0, jj=bindings.length; j{{1+2}}')($rootScope);
- expect(element.hasClass('ng-binding')).toBe(true);
- expect(element.data('$binding')[0].exp).toEqual('{{1+2}}');
- }));
+ expect(element.hasClass('ng-binding')).toBe(false);
+ expect(element.data('$binding')).toBeUndefined();
+ });
+ });
+ it('should occur if `debugInfoEnabled` is true', function() {
+ module(function($compileProvider) {
+ $compileProvider.debugInfoEnabled(true);
+ });
+
+ inject(function($compile, $rootScope) {
+ element = $compile('{{1+2}}
')($rootScope);
+ expect(element.hasClass('ng-binding')).toBe(true);
+ expect(element.data('$binding')).toEqual(['1+2']);
+ });
+ });
+ });
+
it('should observe interpolated attrs', inject(function($rootScope, $compile) {
$compile('')($rootScope);
@@ -4230,12 +4250,17 @@ describe('$compile', function() {
- it('should not leak if two "element" transclusions are on the same element', function () {
+ it('should not leak if two "element" transclusions are on the same element (with debug info)', function () {
if (jQuery) {
// jQuery 2.x doesn't expose the cache storage.
return;
}
+
+ module(function($compileProvider) {
+ $compileProvider.debugInfoEnabled(true);
+ });
+
inject(function($compile, $rootScope) {
expect(jqLiteCacheSize()).toEqual(0);
@@ -4257,12 +4282,48 @@ describe('$compile', function() {
});
- it('should not leak if two "element" transclusions are on the same element', function () {
+ it('should not leak if two "element" transclusions are on the same element (without debug info)', function () {
+ if (jQuery) {
+ // jQuery 2.x doesn't expose the cache storage.
+ return;
+ }
+
+
+ module(function($compileProvider) {
+ $compileProvider.debugInfoEnabled(false);
+ });
+
+ inject(function($compile, $rootScope) {
+ expect(jqLiteCacheSize()).toEqual(0);
+
+ element = $compile('')($rootScope);
+ expect(jqLiteCacheSize()).toEqual(0);
+
+ $rootScope.$apply('xs = [0,1]');
+ expect(jqLiteCacheSize()).toEqual(0);
+
+ $rootScope.$apply('xs = [0]');
+ expect(jqLiteCacheSize()).toEqual(0);
+
+ $rootScope.$apply('xs = []');
+ expect(jqLiteCacheSize()).toEqual(0);
+
+ element.remove();
+ expect(jqLiteCacheSize()).toEqual(0);
+ });
+ });
+
+
+ it('should not leak if two "element" transclusions are on the same element (with debug info)', function () {
if (jQuery) {
// jQuery 2.x doesn't expose the cache storage.
return;
}
+ module(function($compileProvider) {
+ $compileProvider.debugInfoEnabled(true);
+ });
+
inject(function($compile, $rootScope) {
expect(jqLiteCacheSize()).toEqual(0);
element = $compile('')($rootScope);
@@ -5200,7 +5261,9 @@ describe('$compile', function() {
}));
});
inject(function($compile) {
- element = $compile('')($rootScope);
+ // We need to wrap the transclude directive's element in a parent element so that the
+ // cloned element gets deallocated/cleaned up correctly
+ element = $compile('')($rootScope);
expect(capturedTranscludeCtrl).toBeTruthy();
});
});
diff --git a/test/ng/directive/inputSpec.js b/test/ng/directive/inputSpec.js
index 98bc3537e754..ec09d76e07d8 100644
--- a/test/ng/directive/inputSpec.js
+++ b/test/ng/directive/inputSpec.js
@@ -799,6 +799,8 @@ describe('ngModel', function() {
expect(element).toBeInvalid();
expect(element).toHaveClass('ng-invalid-required');
+
+ dealoc(element);
}));
diff --git a/test/ng/directive/ngBindSpec.js b/test/ng/directive/ngBindSpec.js
index 704932b09085..2a1f00637044 100644
--- a/test/ng/directive/ngBindSpec.js
+++ b/test/ng/directive/ngBindSpec.js
@@ -122,13 +122,6 @@ describe('ngBind*', function() {
describe('ngBindHtml', function() {
- it('should add ng-binding class to the element in compile phase', inject(function($compile) {
- var element = jqLite('');
- $compile(element);
- expect(element.hasClass('ng-binding')).toBe(true);
- }));
-
-
describe('SCE disabled', function() {
beforeEach(function() {
module(function($sceProvider) { $sceProvider.enabled(false); });
diff --git a/test/ng/directive/ngClassSpec.js b/test/ng/directive/ngClassSpec.js
index a00708f18d5d..e6d1c134f8bd 100644
--- a/test/ng/directive/ngClassSpec.js
+++ b/test/ng/directive/ngClassSpec.js
@@ -3,6 +3,9 @@
describe('ngClass', function() {
var element;
+ beforeEach(module(function($compileProvider) {
+ $compileProvider.debugInfoEnabled(false);
+ }));
afterEach(function() {
dealoc(element);
@@ -39,9 +42,9 @@ describe('ngClass', function() {
}));
- it('should support adding multiple classes conditionally via a map of class names to boolean' +
+ it('should support adding multiple classes conditionally via a map of class names to boolean ' +
'expressions', inject(function($rootScope, $compile) {
- var element = $compile(
+ element = $compile(
'' +
'
')($rootScope);
@@ -63,7 +66,7 @@ describe('ngClass', function() {
it('should remove classes when the referenced object is the same but its property is changed',
inject(function($rootScope, $compile) {
- var element = $compile('')($rootScope);
+ element = $compile('')($rootScope);
$rootScope.classes = { A: true, B: true };
$rootScope.$digest();
expect(element.hasClass('A')).toBeTruthy();
@@ -124,7 +127,7 @@ describe('ngClass', function() {
$rootScope.$digest();
$rootScope.dynCls = 'foo';
$rootScope.$digest();
- expect(element[0].className).toBe('ui-panel ui-selected ng-scope foo');
+ expect(element[0].className).toBe('ui-panel ui-selected foo');
}));
@@ -132,7 +135,7 @@ describe('ngClass', function() {
element = $compile('')($rootScope);
$rootScope.dynCls = 'panel';
$rootScope.$digest();
- expect(element[0].className).toBe('panel bar ng-scope');
+ expect(element[0].className).toBe('panel bar');
}));
@@ -142,7 +145,7 @@ describe('ngClass', function() {
$rootScope.$digest();
$rootScope.dynCls = 'window';
$rootScope.$digest();
- expect(element[0].className).toBe('bar ng-scope window');
+ expect(element[0].className).toBe('bar window');
}));
@@ -153,7 +156,7 @@ describe('ngClass', function() {
element.addClass('foo');
$rootScope.dynCls = '';
$rootScope.$digest();
- expect(element[0].className).toBe('ng-scope');
+ expect(element[0].className).toBe('');
}));
@@ -161,7 +164,7 @@ describe('ngClass', function() {
element = $compile('')($rootScope);
$rootScope.dynCls = [undefined, null];
$rootScope.$digest();
- expect(element[0].className).toBe('ng-scope');
+ expect(element[0].className).toBe('');
}));
@@ -364,10 +367,14 @@ describe('ngClass', function() {
describe('ngClass animations', function() {
var body, element, $rootElement;
+ afterEach(function() {
+ dealoc(element);
+ });
+
it("should avoid calling addClass accidentally when removeClass is going on", function() {
module('ngAnimateMock');
inject(function($compile, $rootScope, $animate, $timeout) {
- var element = angular.element('');
+ element = angular.element('');
var body = jqLite(document.body);
body.append(element);
$compile(element)($rootScope);
@@ -435,7 +442,7 @@ describe('ngClass animations', function() {
digestQueue.shift()();
$rootScope.val = 'crazy';
- var element = angular.element('');
+ element = angular.element('');
jqLite($document[0].body).append($rootElement);
$compile(element)($rootScope);
@@ -482,7 +489,7 @@ describe('ngClass animations', function() {
$rootScope.two = true;
$rootScope.three = true;
- var element = angular.element('');
+ element = angular.element('');
$compile(element)($rootScope);
$rootScope.$digest();