@@ -380,3 +380,106 @@ restrict: 'E',
380380replace: 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+
0 commit comments