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

Commit c3ffb2f

Browse files
committed
docs($compile): add double compilation known issue
Related #15278
1 parent f582f9e commit c3ffb2f

File tree

1 file changed

+97
-0
lines changed

1 file changed

+97
-0
lines changed

src/ng/compile.js

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -943,6 +943,103 @@
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 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:
956+
*
957+
* ```
958+
angular.module('app').directive('addInput', function($compile) {
959+
return {
960+
link: function(scope, element, attrs) {
961+
var newEl = angular.element('<input ng-model="$ctrl.value">');
962+
attrs.$set('addInput', null) // To stop infinite compile loop
963+
element.append(newEl);
964+
$compile(element)(scope); // Double compilation
965+
}
966+
}
967+
})
968+
```
969+
* At first glance, it looks like removing the original `addInput` attribute is all there is needed
970+
* to make this example work.
971+
* However, if the directive element or its children have other directives attached, they will be compiled and
972+
* linked again, because the compiler doesn't keep track of which directives have been assigned to which
973+
* elements.
974+
*
975+
* This can cause unpredictable behavior, e.g. `ngModel` $formatters and $parsers will be
976+
* attached again to the ngModelController. It can also degrade performance, as
977+
* watchers for text interpolation are added twice to the scope.
978+
*
979+
* Double compilation should therefore avoided. In the above example, the better way is to only
980+
* compile the new element:
981+
* ```
982+
angular.module('app').directive('addInput', function($compile) {
983+
return {
984+
link: function(scope, element, attrs) {
985+
var newEl = angular.element('<input ng-model="$ctrl.value">');
986+
$compile(newEl)(scope); // Only compile the new element
987+
element.append(newEl);
988+
}
989+
}
990+
})
991+
```
992+
*
993+
* Another scenario is adding a directive programmatically to a compiled element and then executing
994+
* compile again.
995+
* ```html
996+
* <input ng-model="$ctrl.value" add-options>
997+
* ```
998+
*
999+
```
1000+
angular.module('app').directive('addOptions', function($compile) {
1001+
return {
1002+
link: function(scope, element, attrs) {
1003+
attrs.$set('addInput', null) // To stop infinite compile loop
1004+
attrs.$set('ngModelOptions', '{debounce: 1000}');
1005+
$compile(element)(scope); // Double compilation
1006+
}
1007+
}
1008+
});
1009+
```
1010+
*
1011+
* In that case, it is necessary to intercept the *initial* compilation of the element:
1012+
*
1013+
* 1. give your directive the `terminal` property and a higher priority than directives
1014+
* that should not be compiled twice. In the example, the compiler will only compile directives
1015+
* which have a priority of 100 or higher.
1016+
* 2. inside this directive's compile function, remove the original directive attribute from the element,
1017+
* and add any other directive attributes. Removing the attribute is necessary, because otherwise the
1018+
* compilation would result in an infinite loop.
1019+
* 3. compile the element but restrict the maximum priority, so that any already compiled directives
1020+
* are not compiled twice.
1021+
* 4. in the link function, link the compiled element with the element's scope
1022+
*
1023+
* ```
1024+
angular.module('app').directive('addOptions', function($compile) {
1025+
return {
1026+
priority: 100, // ngModel has priority 1
1027+
terminal: true,
1028+
template: '<input ng-model="$ctrl.value">',
1029+
compile: function(templateElement, templateAttributes) {
1030+
templateAttributes.$set('addOptions', null);
1031+
templateAttributes.$set('ngModelOptions', '{debounce: 1000}');
1032+
1033+
var compiled = $compile(templateElement, null, 100);
1034+
1035+
return function linkFn(scope) {
1036+
compiled(scope) // Link compiled element to scope
1037+
}
1038+
}
1039+
}
1040+
});
1041+
```
1042+
*
9461043
*/
9471044

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

0 commit comments

Comments
 (0)