Search code examples
javascriptangularjsangularjs-directiveangularjs-compilejqlite

AngularJS: Why can't I add an attribute to grandparent element when parent has ngIf directive


I'm using vanilla AngularJS v1.4.5 (no jQuery) and would like my custom directive to add an attribute to its grandparent element at compile time.

In the compile function, I can achieve this using the parent() method of element twice to get the grandparent element, and the attr() method to add my attribute. However, if the parent element has the ngIf directive, the grandparent element does not get the attribute.

angular.module('myApp', [])
    .directive('foo', fooDirective)
;

function fooDirective() {
    return {
        compile : compile,
        priority: 601 // ngIf is 600
    };

    function compile(element, attrs) {
        var parent, grandparent;

        parent = element.parent();            
        grandparent = parent.parent();

        parent.attr('foo', 'bar');
        grandparent.attr('foo', 'bar');
    }
}

JSFiddle

Here's what I know:

  • If ngIf is not used on the parent element, the attribute gets added to the grandparent.
  • The problem should not be related to scope, since this is taking place during the compile phase, before scope has been linked to any elements.
  • My compile function should be running before that of ngIf, which has a priority of 600 (and doesn't have a compile function).
  • ngIf completely removes and recreates the element in the DOM (along with its child elements), but that should not affect the grandparent element or change it's attributes.

Can anyone explain to me why I cannot add an attribute to my directive's grandparent element if the parent element has the ngIf directive?


Solution

  • So, to restate, the question is, why given the following:

    <grand-parent>
      <parent ng-if="condition">
        <foo></foo>
      </parent>
    </grand-parent>
    

    when attempting to retrieve var grandparent = tElement.parent().parent() from within a compile of foo, grandparent doesn't refer to the <grand-parent> element.

    The answer is because of ngIf-caused transclusion, even if condition === true.

    Transclusion is the process where the contents (or the element + the contents, depending on the type of transclusion) are yanked out of DOM, compiled, and then made available, as a clone, to a transclusion function, which by itself is available as the 5th parameter of link function:

    link: function(scope, element, attrs, ctrls, transcludeFn){
      transcludeFn(scope, function cloneAttachFn(clonedContent){
    
        // clonedContent is the subtree that was transcluded, compiled and cloned
        element.append(clonedContent);
      });
    }
    

    So, the compilation pass starts at <grand-parent>, then goes to <parent>, where it sees a directive - ngIf. Because ngIf has transclude: "element", it yanks <parent><foo></foo></parent> out of DOM, and compiles that. And so, the compilation proceeds to compile other lower-priority directives (if available) on <parent>, then compiles the foo directive.

    At this point, <foo> is not under <grand-parent> and tElement.parent().parent() yields [].