4

I'm currently writing some Angular directives that change their templates depending on certain attributes passed. They look structurally like this:

app.directive('mydir', function ($compile) {
    return {
        restrict: 'E',
        replace: true,
        transclude: true,
        compile: function (elem, attrs, transclude) {
            var replacement;
            // operations
            return function (scope, lElem, lAttrs) {
                transclude(scope, function (clone, scope) {
                    // more operations
                });
                elem.replaceWith($compile(replacement)(scope));  // IMPORTANT
            };
        }
    };
});

and are on this Plunkr. Now, as written (well, fleshed out), these directives don't work if you try to nest them. The inner directive ends up with its transcluded elements entirely erased. However, if I replace elem.replaceWith(...) with lElem.replaceWith(...) (on line 36 of the Javascript file in the Plunkr) everything is fine and I can nest as many times as I want. Why is this happening? What is the difference here? I understand that elem is not compiled and lElem is, but what does that actually mean and how is it affecting my code? Furthermore, isn't it discouraged to do DOM manipulations in the link function?

What I've tried. I've tried to debug this myself and it looks like for the inner directive the compile function is called twice (I'd assume once from whatever angular is doing and once from when I use $compile on it), that the first time it compiles everything is correct, and the second time everything is wrong. But I can't get it to only compile once without removing the $compile statement, which breaks everything! It also looks like Angular compiles everything "on the way up" (i.e. children first) and links everything "on the way down" (i.e. parents first), but I can't confirm that. On a hunch I tried returning a prelink function instead of a postlink function, to no avail. I've also tried putting the elem.replaceWith(...) call before the link function and compiling the children inside the link function, but that didn't work either.

What I think is happening. I think that putting the transclusion in the link function is messing everything up (though I am forced to because compile has no scope) by taking the old clone inside the directive, and removing its children for some reason (elem and lElem have no children when referenced, for instance). I'm not sure how to confirm this or how to fix it once it's confirmed. I preliminarily did some testing by injecting $rootScope and doing all the linking, etc in the compile function, and nothing worked. I'm not sure if there are any differences between compiling that and compiling with the child scope, though.

jclancy
  • 49,598
  • 5
  • 30
  • 34
  • Performing DOM manipulations inside a directive (e.g., in a compile and/or link function) is the right place to do such. What is discouraged is doing DOM manipulations elsewhere, e.g., in a controller. – Mark Rajcok Jul 09 '13 at 01:21
  • 2
    Here's a [plunker](http://plnkr.co/edit/qrDMJBlnwdNlfBqEEXL2?p=preview) that shows when the compile, pre-link, and post-link functions run with nested directives, if that helps any. – Mark Rajcok Jul 09 '13 at 01:36
  • @MarkRajcok That was very helpful, it's interesting that compilation, controller instantiation, and prelink happen top-down, but postlink happens bottom-up. Would it be fair to say that when I replace `elem` instead of `lElem` I actually use an element that hasn't been transcluded, effectively skipping the compile-link step for the inner directive? – jclancy Jul 09 '13 at 03:38
  • You should only perform DOM manipulations inside the post-link compile function (aka what the link function really is). Not the controller and not the pre-link. – tommybananas Feb 11 '14 at 03:47
  • @MarkRajcok that plunker is a real gem hidden away here; thanks for that – Carl Sharman Feb 12 '14 at 15:51
  • In linking function you should never touch your compile template. It look like you're replacing not compiled html with your compiled one and it make compile happen again. – jcubic Jul 04 '14 at 20:37
  • Perhaps a more comprehensive overview of directive functions: [Angular directives - when to use compile, controller, pre-link and post-link](http://stackoverflow.com/questions/24615103). – Izhaki Jul 07 '14 at 16:41

0 Answers0