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

Commit af8f311

Browse files
committed
move to the compiler guide
1 parent 481176d commit af8f311

File tree

2 files changed

+111
-102
lines changed

2 files changed

+111
-102
lines changed

docs/content/guide/compiler.ngdoc

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -380,3 +380,110 @@ 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 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 mouseout', 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 mouseout', function() {
427+
scope.$apply('showHint = !showHint');
428+
});
429+
430+
attrs.$set('addMouseover', null);
431+
element.append(newEl);
432+
$compile(newEl)(scope); // Only compile the new element
433+
}
434+
}
435+
})
436+
```
437+
438+
Another scenario is adding a directive programmatically to a compiled element and then executing
439+
compile again.
440+
441+
```html
442+
<input ng-model="$ctrl.value" add-options>
443+
```
444+
445+
```
446+
angular.module('app').directive('addOptions', function($compile) {
447+
return {
448+
link: function(scope, element, attrs) {
449+
attrs.$set('addOptions', null) // To stop infinite compile loop
450+
attrs.$set('ngModelOptions', '{debounce: 1000}');
451+
$compile(element)(scope); // Double compilation
452+
}
453+
}
454+
});
455+
```
456+
457+
In that case, it is necessary to intercept the *initial* compilation of the element:
458+
459+
1. Give your directive the `terminal` property and a higher priority than directives
460+
that should not be compiled twice. In the example, the compiler will only compile directives
461+
which have a priority of 100 or higher.
462+
2. Inside this directive's compile function, remove the original directive attribute from the element,
463+
and add any other directive attributes. Removing the attribute is necessary, because otherwise the
464+
compilation would result in an infinite loop.
465+
3. Compile the element but restrict the maximum priority, so that any already compiled directives
466+
are not compiled twice.
467+
4. In the link function, link the compiled element with the element's scope
468+
469+
```
470+
angular.module('app').directive('addOptions', function($compile) {
471+
return {
472+
priority: 100, // ngModel has priority 1
473+
terminal: true,
474+
template: '<input ng-model="$ctrl.value">',
475+
compile: function(templateElement, templateAttributes) {
476+
templateAttributes.$set('ngModelOptions', '{debounce: 1000}');
477+
478+
// The third argument is the max priority. Only directives with priority < 100 will be compiled,
479+
// therefore we don't need to remove the attribute
480+
var compiled = $compile(templateElement, null, 100);
481+
482+
return function linkFn(scope) {
483+
compiled(scope) // Link compiled element to scope
484+
}
485+
}
486+
}
487+
});
488+
```
489+

src/ng/compile.js

Lines changed: 4 additions & 102 deletions
Original file line numberDiff line numberDiff line change
@@ -948,108 +948,10 @@
948948
*
949949
* ### Double Compilation
950950
*
951-
* Double compilation occurs when an already compiled part of the DOM gets compiled again. This is
952-
* not an intended use case and can lead to misbehaving directives, performance issues, and memory
953-
* leaks.
954-
* A common scenario where this happens is a directive that calls `$compile` in a directive link
955-
* function on the directive element. In the following example, a directive adds a mouseover behavior
956-
* to a button with `ngClick` on it:
957-
*
958-
* ```
959-
angular.module('app').directive('addMouseover', function($compile) {
960-
return {
961-
link: function(scope, element, attrs) {
962-
var newEl = angular.element('<span ng-show="showHint"> My Hint</span>');
963-
element.on('mouseenter mouseout', function() {
964-
scope.$apply('showHint = !showHint');
965-
});
966-
967-
attrs.$set('addMouseover', null); // To stop infinite compile loop
968-
element.append(newEl);
969-
$compile(element)(scope); // Double compilation
970-
}
971-
}
972-
})
973-
```
974-
* At first glance, it looks like removing the original `addMouseover` attribute is all there is needed
975-
* to make this example work.
976-
* However, if the directive element or its children have other directives attached, they will be compiled and
977-
* linked again, because the compiler doesn't keep track of which directives have been assigned to which
978-
* elements.
979-
*
980-
* This can cause unpredictable behavior, e.g. `ngClick` or other event handlers will be attached
981-
* again. It can also degrade performance, as watchers for text interpolation are added twice to the scope.
982-
*
983-
* Double compilation should therefore be avoided. In the above example, only the new element should
984-
* be compiled:
985-
*
986-
* ```
987-
angular.module('app').directive('addMouseover', function($compile) {
988-
return {
989-
link: function(scope, element, attrs) {
990-
var newEl = angular.element('<span ng-show="showHint"> My Hint</span>');
991-
element.on('mouseenter mouseout', function() {
992-
scope.$apply('showHint = !showHint');
993-
});
994-
995-
attrs.$set('addMouseover', null);
996-
element.append(newEl);
997-
$compile(newEl)(scope); // Only compile the new element
998-
}
999-
}
1000-
})
1001-
```
1002-
*
1003-
* Another scenario is adding a directive programmatically to a compiled element and then executing
1004-
* compile again.
1005-
* ```html
1006-
* <input ng-model="$ctrl.value" add-options>
1007-
* ```
1008-
*
1009-
```
1010-
angular.module('app').directive('addOptions', function($compile) {
1011-
return {
1012-
link: function(scope, element, attrs) {
1013-
attrs.$set('addOptions', null) // To stop infinite compile loop
1014-
attrs.$set('ngModelOptions', '{debounce: 1000}');
1015-
$compile(element)(scope); // Double compilation
1016-
}
1017-
}
1018-
});
1019-
```
1020-
*
1021-
* In that case, it is necessary to intercept the *initial* compilation of the element:
1022-
*
1023-
* 1. Give your directive the `terminal` property and a higher priority than directives
1024-
* that should not be compiled twice. In the example, the compiler will only compile directives
1025-
* which have a priority of 100 or higher.
1026-
* 2. Inside this directive's compile function, remove the original directive attribute from the element,
1027-
* and add any other directive attributes. Removing the attribute is necessary, because otherwise the
1028-
* compilation would result in an infinite loop.
1029-
* 3. Compile the element but restrict the maximum priority, so that any already compiled directives
1030-
* are not compiled twice.
1031-
* 4. In the link function, link the compiled element with the element's scope
1032-
*
1033-
* ```
1034-
angular.module('app').directive('addOptions', function($compile) {
1035-
return {
1036-
priority: 100, // ngModel has priority 1
1037-
terminal: true,
1038-
template: '<input ng-model="$ctrl.value">',
1039-
compile: function(templateElement, templateAttributes) {
1040-
templateAttributes.$set('ngModelOptions', '{debounce: 1000}');
1041-
1042-
// The third argument is the max priority. Only directives with priority < 100 will be compiled,
1043-
// therefore we don't need to remove the attribute
1044-
var compiled = $compile(templateElement, null, 100);
1045-
1046-
return function linkFn(scope) {
1047-
compiled(scope) // Link compiled element to scope
1048-
}
1049-
}
1050-
}
1051-
});
1052-
```
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.
1053955
*
1054956
*/
1055957

0 commit comments

Comments
 (0)