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

Commit 1f61f30

Browse files
Narretzpetebacondarwin
authored andcommitted
docs($compile, guide/compiler): add "double compilation" known issue
Related #15278 Closes #15392
1 parent c57779d commit 1f61f30

File tree

2 files changed

+113
-0
lines changed

2 files changed

+113
-0
lines changed

docs/content/guide/compiler.ngdoc

+103
Original file line numberDiff line numberDiff line change
@@ -380,3 +380,106 @@ restrict: 'E',
380380
replace: true
381381
```
382382

383+
### Double Compilation, and how to avoid it
384+
385+
Double compilation occurs when an already compiled part of the DOM gets compiled again. This is an
386+
undesired effect and can lead to misbehaving directives, performance issues, and memory
387+
leaks.
388+
A common scenario where this happens is a directive that calls `$compile` in a directive link
389+
function on the directive element. In the following **faulty example**, a directive adds a mouseover behavior
390+
to a button with `ngClick` on it:
391+
392+
```
393+
angular.module('app').directive('addMouseover', function($compile) {
394+
return {
395+
link: function(scope, element, attrs) {
396+
var newEl = angular.element('<span ng-show="showHint"> My Hint</span>');
397+
element.on('mouseenter mouseleave', function() {
398+
scope.$apply('showHint = !showHint');
399+
});
400+
401+
attrs.$set('addMouseover', null); // To stop infinite compile loop
402+
element.append(newEl);
403+
$compile(element)(scope); // Double compilation
404+
}
405+
}
406+
})
407+
```
408+
409+
At first glance, it looks like removing the original `addMouseover` attribute is all there is needed
410+
to make this example work.
411+
However, if the directive element or its children have other directives attached, they will be compiled and
412+
linked again, because the compiler doesn't keep track of which directives have been assigned to which
413+
elements.
414+
415+
This can cause unpredictable behavior, e.g. `ngClick` or other event handlers will be attached
416+
again. It can also degrade performance, as watchers for text interpolation are added twice to the scope.
417+
418+
Double compilation should therefore be avoided. In the above example, only the new element should
419+
be compiled:
420+
421+
```
422+
angular.module('app').directive('addMouseover', function($compile) {
423+
return {
424+
link: function(scope, element, attrs) {
425+
var newEl = angular.element('<span ng-show="showHint"> My Hint</span>');
426+
element.on('mouseenter mouseleave', function() {
427+
scope.$apply('showHint = !showHint');
428+
});
429+
430+
element.append(newEl);
431+
$compile(newEl)(scope); // Only compile the new element
432+
}
433+
}
434+
})
435+
```
436+
437+
Another scenario is adding a directive programmatically to a compiled element and then executing
438+
compile again. See the following **faulty example**:
439+
440+
```html
441+
<input ng-model="$ctrl.value" add-options>
442+
```
443+
444+
```
445+
angular.module('app').directive('addOptions', function($compile) {
446+
return {
447+
link: function(scope, element, attrs) {
448+
attrs.$set('addOptions', null) // To stop infinite compile loop
449+
attrs.$set('ngModelOptions', '{debounce: 1000}');
450+
$compile(element)(scope); // Double compilation
451+
}
452+
}
453+
});
454+
```
455+
456+
In that case, it is necessary to intercept the *initial* compilation of the element:
457+
458+
1. Give your directive the `terminal` property and a higher priority than directives
459+
that should not be compiled twice. In the example, the compiler will only compile directives
460+
which have a priority of 100 or higher.
461+
2. Inside this directive's compile function, add any other directive attributes to the template.
462+
3. Compile the element, but restrict the maximum priority, so that any already compiled directives
463+
(including the `addOptions` directive) are not compiled again.
464+
4. In the link function, link the compiled element with the element's scope.
465+
466+
```
467+
angular.module('app').directive('addOptions', function($compile) {
468+
return {
469+
priority: 100, // ngModel has priority 1
470+
terminal: true,
471+
compile: function(templateElement, templateAttributes) {
472+
templateAttributes.$set('ngModelOptions', '{debounce: 1000}');
473+
474+
// The third argument is the max priority. Only directives with priority < 100 will be compiled,
475+
// therefore we don't need to remove the attribute
476+
var compiled = $compile(templateElement, null, 100);
477+
478+
return function linkFn(scope) {
479+
compiled(scope) // Link compiled element to scope
480+
}
481+
}
482+
}
483+
});
484+
```
485+

src/ng/compile.js

+10
Original file line numberDiff line numberDiff line change
@@ -943,6 +943,16 @@
943943
*
944944
* For information on how the compiler works, see the
945945
* {@link guide/compiler Angular HTML Compiler} section of the Developer Guide.
946+
*
947+
* @knownIssue
948+
*
949+
* ### Double Compilation
950+
*
951+
Double compilation occurs when an already compiled part of the DOM gets
952+
compiled again. This is an undesired effect and can lead to misbehaving directives, performance issues,
953+
and memory leaks. Refer to the Compiler Guide {@link guide/compiler#double-compilation-and-how-to-avoid-it
954+
section on double compilation} for an in-depth explanation and ways to avoid it.
955+
*
946956
*/
947957

948958
var $compileMinErr = minErr('$compile');

0 commit comments

Comments
 (0)