The compiler will no longer throw if a directive template contains comment nodes in addition to a
single root node. If a template contains less than 2 nodes, the nodes are unaltered.
BREAKING CHANGE:
If a template contains directives within comment nodes, and there is more than a single node in the
template, those comment nodes are removed. The impact of this breaking change is expected to be
quite low.
Closes#9212Closes#9215
This fixes the case when a directive that uses `templateUrl`
is used somewhere in the children
of a transcluding directive like `ng-repeat`.
Fixes#9344
Related to #8808Closes#9415
Because the regex that tests the `require` value will match more than just `^^?`,
it is important to test other common ways to specify a controller requirement
to ensure that a breaking change isn't introduced inadvertently. This adds a test
for `?^^`.
Closes#9389Closes#9390
Prevent accidentally treating a builtin function from Object.prototype as the binding object, and thus
preventing the compiler from throwing when using attribute binding names which match a property of the
Object prototype.
Closes#9343Closes#9345
Transcluded scopes are now connected to the scope in which they are created
via their `$parent` property. This means that they will be automatically destroyed
when their "containing" scope is destroyed, without having to resort to listening
for a `$destroy` event on various DOM elements or other scopes.
Previously, transclude scope not only inherited prototypically from the scope from
which they were transcluded but they were also still owned by that "outer" scope.
This meant that there were scenarios where the "real" container scope/element was
destroyed but the transclude scope was not, leading to memory leaks.
The original strategy for dealing with this was to attach a `$destroy` event handler
to the DOM elements in the transcluded content, so that if the elements were removed
from the DOM then their associated transcluded scope would be destroyed.
This didn't work for transclude contents that didn't contain any elements - most
importantly in the case of the transclude content containing an element transclude
directive at its root, since the compiler swaps out this element for a comment
before a destroy handler could be attached.
BREAKING CHANGE:
`$transclude` functions no longer attach `$destroy` event handlers to the
transcluded content, and so the associated transclude scope will not automatically
be destroyed if you remove a transcluded element from the DOM using direct DOM
manipulation such as the jquery `remove()` method.
If you want to explicitly remove DOM elements inside your directive that have
been compiled, and so potentially contain child (and transcluded) scopes, then
it is your responsibility to get hold of the scope and destroy it at the same time.
The suggested approach is to create a new child scope of your own around any DOM
elements that you wish to manipulate in this way and destroy those scopes if you
remove their contents - any child scopes will then be destroyed and cleaned up
automatically.
Note that all the built-in directives that manipulate the DOM (ngIf, ngRepeat,
ngSwitch, etc) already follow this best practice, so if you only use these for
manipulating the DOM then you do not have to worry about this change.
Closes#9095Closes#9281
Implement option to strengthen require '^' operator, by adding another '^'.
When a second '^' is used, the controller will only search parent nodes for the
matching controller, and will throw or return null if not found, depending on
whether or not the requirement is optional.
Closes#4518Closes#4540Closes#8240Closes#8511
'@'-bindings were previously updating the scope when they ought to have been
updating the controller (requested via `bindToController: true` + controllerAs).
It's a one-line fix + test case.
Closes#9052Closes#9077
This reverts commit 6d1e7cdc51.
This commit was causing breakages because of its assumption that transcluded
content would be handled predictably, i.e. with ngTransclude, whereas many
use cases involve manipulating transcluded content in linking functions.
It is now possible to ask the $compiler's isolate scope property machinery to bind isolate
scope properties to a controller rather than scope itself. This feature requires the use of
controllerAs, so that the controller-bound properties may still be referenced from binding
expressions in views.
The current syntax is to prefix the scope name with a '@', like so:
scope: {
"myData": "=someData",
"myString": "@someInterpolation",
"myExpr": "&someExpr"
},
controllerAs: "someCtrl",
bindtoController: true
The putting of properties within the context of the controller will only occur if
controllerAs is used for an isolate scope with the `bindToController` property of the
directive definition object set to `true`.
Closes#7635Closes#7645
The compiler adds scope information (`ng-scope` CSS class and `$scope` data property) to elements
when the are bound to the scope. This is mostly to aid debugging tools such as Batarang. In
production this should be unnecesary and adds a performance penalty.
In the bench/apps/largetable-bp this change caused an improvement of ~100ms (7%).
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 ~120ms, that is ~10%.
Measuring the "create" phase, 25 loops, mean time ~1200ms -> ~1080ms.
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 `{{ ... }}`.
All class-based animation methods (addClass, removeClass and setClass) on $animate
are now processed after the next digest occurs. This fix prevents any sequencing
errors from occuring from excessive calls to $animate.addClass, $animate.remoteClass
or $animate.setClass.
BREAKING CHANGE
$animate.addClass, $animate.removeClass and $animate.setClass will no longer start the animation
right after being called in the directive code. The animation will only commence once a digest
has passed. This means that all animation-related testing code requires an extra digest to kick
off the animation.
```js
//before this fix
$animate.addClass(element, 'super');
expect(element).toHaveClass('super');
//now
$animate.addClass(element, 'super');
$rootScope.$digest();
expect(element).toHaveClass('super');
```
$animate will also tally the amount of times classes are added and removed and only animate
the left over classes once the digest kicks in. This means that for any directive code that
adds and removes the same CSS class on the same element then this may result in no animation
being triggered at all.
```js
$animate.addClass(element, 'klass');
$animate.removeClass(element, 'klass');
$rootScope.$digest();
//nothing happens...
```
Via transclusion, svg elements can occur outside an `<svg>` container in an
Angular template but are put into an `<svg>` container through compilation
and linking.
E.g.
Given that `svg-container` is a transcluding directive with
the following template:
```
<svg ng-transclude></svg>
```
The following markup creates a `<circle>` inside of an `<svg>` element
during runtime:
```
<svg-container>
<circle></circle>
</svg-container>
```
However, this produces non working `<circle>` elements, as svg elements
need to be created inside of an `<svg>` element.
This change detects for most cases the correct namespace of transcluded content
and recreates that content in the correct `<svg>` container
when needed during compilation. For special cases it adds an addition argument
to `$transclude` that allows to specify the future parent node of elements
that will be cloned and attached using the `cloneAttachFn`.
Related to #8494Closes#8716
Also corrects the tests for MathML that use `directive.templateNamespace`.
BREAKING CHANGE (within 1.3.0-beta): `directive.type` was renamed to `directive.templateNamespace`
The property name `type` was too general.
allOrNothing interpolation is now used for ng-attr-*, under all circumstances. This prevents
uninitialized attributes from being added to the DOM with invalid values which cause errors
to be shown.
BREAKING CHANGE:
Now, ng-attr-* will never add the attribute to the DOM if any of the interpolated expressions
evaluate to `undefined`.
To work around this, initialize values which are intended to be the empty string with the
empty string:
For example, given the following markup:
<div ng-attr-style="border-radius: {{value}}{{units}}"></div>
If $scope.value is `4`, and $scope.units is undefined, the resulting markup is unchanged:
<div ng-attr-style="border-radius: {{value}}{{units}}"></div>
However, if $scope.units is `""`, then the resulting markup is updated:
<div ng-attr-style="border-radius: {{value}}{{units}}" style="border-radius: 4"></div>
Closes#8376Closes#8399
It is now possible to ask the $compiler's isolate scope property machinery to bind isolate
scope properties to a controller rather than scope itself. This feature requires the use of
controllerAs, so that the controller-bound properties may still be referenced from binding
expressions in views.
The current syntax is to prefix the scope name with a '@', like so:
scope: {
"myData": "=someData",
"myString": "@someInterpolation",
"myExpr": "&someExpr"
},
controllerAs: "someCtrl",
bindtoController: true
The putting of properties within the context of the controller will only occur if
controllerAs is used for an isolate scope with the `bindToController` property of the
directive definition object set to `true`.
Closes#7635Closes#7645
.context is a deprecated jQuery api still being used by at least live() queries, so
we need to keep it in up to date during replacement.
Because of the if check, we can be sure that we replace the context only when jQuery is being
used and the context property is set to the element being replaced.
Closes#8253Closes#7900
This reverts commit 0d608d041f.
The commits caused more breaking changes at Google than initially expected and since its
benefit is small, so it's not worth keeping.
Previously we would do it manually in all of our structural directives.
BREAKING CHANGE: element-transcluded directives now have an extra comment automatically appended to their cloned DOM
This comment is usually needed to keep track the end boundary in the event child directives modify the root node(s).
If not used for this purpose it can be safely ignored.
This is a major perf win in the large table benchmark (~100ms or 9).
This cleanup is needed only for regular transclusion because only then the DOM hierarchy doesn't match scope hierarchy
(transcluded scope is a child of the parent scope and not a child of the isolate scope)
We should consider refactoring this further for the case of regular transclusion
and consider using scope events instead.
Instead of using a counter and an extra watch, just schedule the reaction function via .
This gives us the same/similar ordering and coalecsing of updates as counter without the extra
overhead. Also the code is easier to read.
Since interpolation uses watchGroup, this change additionally improves performance of interpolation.
In large table benchmark digest cost went down by 15-20% for interpolation.
Closes#8396
Some libraries (like jQuery UI) patch jQuery.cleanData as well. This commit
makes Angular work correctly even if such external patching was done after
the Angular one.
Fixes#8471
The data jQuery method was re-implemented in 2.0 in a secure way. This made
current hacky Angular solution to move data between elements via changing the
value of the internal node[jQuery.expando] stop working. Instead, just copy the
data from the first element to the other one.
Testing cache leaks on jQuery 2.x is not possible in the same way as it's done
in jqLite or in jQuery 1.x as there is no publicly exposed data storage. One
way to test it would be to intercept all places where a jQuery object is created
to save a reference to the underlaying node but there is no single place in the
jQuery code through which all element creation passes (there are various
shortcuts for performance reasons). Instead we rely on jqLite.cache testing
to find potential data leaks.
BREAKING CHANGE: Angular no longer supports jQuery versions below 2.1.1.
Instead of using a counter and an extra watch, just schedule the reaction function via $evalAsync.
This gives us the same/similar ordering and coalecsing of updates as counter without the extra
overhead. Also the code is easier to read.
Since interpolation uses watchGroup, this change additionally improves performance of interpolation.
In large table benchmark digest cost went down by 15-20% for interpolation.
Closes#8396
Previously we defaulted just to A because of IE8 which had a hard time with applying css styles to HTMLUnknownElements.
This is no longer the case with IE9, so we should make restrict default to EA. Doing so will make it easier to create
components and avoid matching errors when creating new directives
BREAKING CHANGE: directives now match elements by default unless specific restriction rules are set via `restrict` property.
This means that if a directive 'myFoo' previously didn't specify matching restrictrion, it will now match both the attribute
and element form.
Before:
<div my-foo></div> <---- my-foo attribute matched the directive
<my-foo></my-foo> <---- no match
After:
<div my-foo></div> <---- my-foo attribute matched the directive
<my-foo></my-foo> <---- my-foo element matched the directive
It is not expected that this will be a problem in practice because of widespread use of prefixes that make "<my-foo>" like
elements unlikely.
Closes#8321
Directives which expect to make use of the multi-element grouping feature introduced in
1.1.6 (https://github.com/angular/angular.js/commit/e46100f7) must now add the property multiElement
to their definition object, with a truthy value.
This enables the use of directive attributes ending with the words '-start' and '-end' for
single-element directives.
BREAKING CHANGE: Directives which previously depended on the implicit grouping between
directive-start and directive-end attributes must be refactored in order to see this same behaviour.
Before:
```
<div data-fancy-directive-start>{{start}}</div>
<p>Grouped content</p>
<div data-fancy-directive-end>{{end}}</div>
.directive('fancyDirective', function() {
return {
link: angular.noop
};
})
```
After:
```
<div data-fancy-directive-start>{{start}}</div>
<p>Grouped content</p>
<div data-fancy-directive-end>{{end}}</div>
.directive('fancyDirective', function() {
return {
multiElement: true, // Explicitly mark as a multi-element directive.
link: angular.noop
};
})
```
Closes#5372Closes#6574Closes#5370Closes#8044Closes#7336
BEAKING CHANGE:
Lazy-binding now happens on the scope watcher level.
What this means is that given `parseFn = $parse('::foo')`,
bind-once will only kick in when `parseFn` is being watched by a scope
(i.e. `scope.$watch(parseFn)`)
Bind-once will have no effect when directily invoking `parseFn` (i.e. `parseFn()`)
If an element contains two "element" transcludes then the initial clone
consists of only comment nodes. The concern was that this meant that
the transclude scopes would not be cleaned up.
But it turns out that in the case that there are only comments then the
scope is never attached to anything so we don't need to worry about cleaning
it up.
Later if a concrete element is created as part of the transclude then these
elements will have destroy handlers.
Previously, <element ng-attr-foo="{{binding}}" foo="bar"></element>'s "foo" attribute would always
equal "bar", because the bound version was overwritten. This CL corrects this behaviour and ensures
that the ordering of attributes does not have an effect on whether or not ng-attr-bound attributes
do their work.
Closes#7739
In apps that create lots of scopes (apps with large tables) the uid generation
shows up in the profiler and adds a few milliseconds. Using simple counter
doesn't have this overhead.
I think the initial fear of overflowing and thus using string alphanum sequence
is unjustified because even if an app was to create lots of scopes non-stop,
you could create about 28.6 million scopes per seconds for 10 years before
you would reach a number that can't be accurately represented in JS
BREAKING CHANGE: Scope#$id is now of time number rather than string. Since the
id is primarily being used for debugging purposes this change should not affect
anyone.
If a "replace" directive has an async template, which contains a transclusion
directive at its root node, then outer transclusions were failing to be
passed to this directive. An example would be uses of `ngIf` inside and
outside the template.
Collaborated with @caitp
Closes#7183Closes#7772
Previously, the compiler would throw an error if a directive requested new non-isolate scope
after a directive had requested isolate scope. But it would not error if a directive
requested an isolate scope after a directive had requested a new non-isolate scope.
Since it is invalid to have more than one directive request any kind of scope if one of
them has requested isolate scope, then the compiler should error whatever order the
directives are applied.
This fix addresses this situation by throwing error regardless of order of directives.
BREAKING CHANGE:
Requesting isolate scope and any other scope on a single element is an error.
Before this change, the compiler let two directives request a child scope
and an isolate scope if the compiler applied them in the order of non-isolate
scope directive followed by isolate scope directive.
Now the compiler will error regardless of the order.
If you find that your code is now throwing a `$compile:multidir` error,
check that you do not have directives on the same element that are trying
to request both an isolate and a non-isolate scope and fix your code.
Closes#4402Closes#4421