|
943 | 943 | *
|
944 | 944 | * For information on how the compiler works, see the
|
945 | 945 | * {@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 | + * |
946 | 1043 | */
|
947 | 1044 |
|
948 | 1045 | var $compileMinErr = minErr('$compile');
|
|
0 commit comments