@@ -380,3 +380,106 @@ restrict: 'E',
380
380
replace: true
381
381
```
382
382
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