Is it possible to compile a modified directive template without using transclusion?
Asked Answered
G

2

4

I have been trying to use transclusion to create a directive which makes two copies of it's contents and appends them inside the original template. I have failed in my attempts to modify the two copies before they slotted back into the DOM and I think it's because I have misunderstood how transclusion works.

I have another question here which I don't think is going to be answered because I think the premise may be wrong.

Transclude function needs to modify clone properly

I need to take a new approach to this and I was wondering if it would be sensible to ditch transclusion and get my hands dirty inside a compile function.

My new question is, can you take the contents of "elem", make a couple of copies of it using JQlite then compile them manually against the directive's parent scope and add them back into the original template?

So, if my toolbar directive is used like this, where the contents of the toolbar tag can be any HTML the user wants...

<div ng-controller="myController">
    <toolbar>
        <form name="formName" submit="myController.submit()">
            <div>
                ... some controls...
            </div>
        </form>
    </toolbar>
</div>

And the template for the directive is this....

<toolbar-inner>
    <div class="toolbar">
        <div transclude-main></div>
    </div>
    <div class="overflow">
        <div transclude-overflow></div>
    </div>
</toolbar-inner>

My compile function of the toolbar directive needs to take a copy of the contents of the element, clone it, rename any forms so that we don't have duplicate form names then compile one copy against the parent controller and slot it into the main slot then do the same with a second copy and slot it into the overflow slot.

The key things is that at the end of it I should have a single directive with two copies of it's contents and my controller should have two forms on it - myController.formName and myController.formName2

Please let me know if I haven't explained something correctly here. I'm pretty sure the solution here should not involve transclusion, hence posting the second question. It is not a duplicate.

If I can explain anything in further detail please ask.

Many thanks.

EDIT:

I have tried to do this in the following Plunkr

https://plnkr.co/edit/eUIdaPiOIISDdXGLBTKJ?p=preview

I have a few problems with this:

A) I am getting a JS error in the console - TypeError: Cannot read property 'childNodes' of undefined

B) I was assuming I could just mess with the template in the pre-compile phase and replace the contents of the directive with new HTML consisting of a new template merged with two copies of the original contents. I can see though that I have to compile them against the $parent scope because my directive uses an isolate scope (although not strictly necessary in this cut down example)

C) I get another error when replacing the original contents of the directive element anyway.

I think I am half way there and hopefully this plunk shows what I an prying to achieve, but i have run out of knowledge.

For completeness, here is the plunk where I tried to do it with transclusion, which didn't work because the transcluded contents are already compiled by the time I started messing with them in the transclude function.

https://plnkr.co/edit/XE7REjJRShw43cpfJCh2?p=preview

You can see the full conversation about this in my previous question:

Transclude function needs to modify clone properly

Grivet answered 12/7, 2016 at 15:15 Comment(2)
One should ask why you can not use ng-repeat? Why you prefer modifying html instead of making it template?Outnumber
Because i have no idea what HTML is going to be contained in the directive when it's used. It may contain forms, it may not, it may contain buttons, menus, icons etc. It is up to the author of the application to use my toolbar with whatever contents they want.Grivet
R
2

I got your transcluded example working. See here.

I had to call the below to get it working.

$compile(clone)(scope.$parent);

For the ngTransclude:orphan problem, in this version by compiling just the form elements it works when the child transclude is outside of the form.

Rachealrachel answered 19/7, 2016 at 4:40 Comment(6)
Thanks, will check this properly when I get to work. I didn't know you could "re-compile" the transcluded content after tampering with it. One thing though, I don't think you needed to change my looping function. The aim was not to sequentially number the forms but simply to leave the original name in place in copy 1 and append a "2" to the name of each form in the second copy, which unless I'm mistaken my original code was doing. Also, using javascript loop to walk through the clone, does that go deep into the DOM like .find() does?Grivet
Ah Damn. This failed when I used it in my actual project with the following error. [ngTransclude:orphan] Illegal use of ngTransclude directive in the template! No parent directive that requires a transclusion found. Element: <button-content ng-transclude=""> This seems to not be the directive which is failing but causing a problem with a child directive which uses transclusion. The child directive works perfectly well under all other circumstances, including in this directive if you remove the $compile line from the 'pre' function.Grivet
I updated the plunker to do find as well. For that issue, the problem is that the ng-transclude content is being compiled by our manual call to $compile, so when it is reached the content is already there and there is nothing to transclude. See this question. Maybe, try what JEuvin mentioned, i.e. to have fallback content, that is displayed if no transcluded content is provided.Rachealrachel
Yeah, I replied to JEuvin's comment. Looks like manually compiling at this stage is going to mess things up with child directives, especially 3rd part ones. I know it's possible because the normal transclude functionality must be compiling the contents before giving it to you as the clone in the transcludeFn.Grivet
Are you adding the components with transcluded content into the form because in the updated example it works if these components are outside of the form as they don't get compiled.Rachealrachel
The transcluded content could be literally anything as I don't know what controls my colleagues will want to put inside a toolbar, but in my example I had a form inside my toolbar and there was a button control inside the form (the form submit button) which itself used transclusion. I guess my problem is that my toolbar component needs to deal with any content, including other directives which may use transclusion and may not be under my control. At the same time, when duplicating forms if I don't change the name attributes before compilation then I only get one form on my outer controller.Grivet
C
1

This plunker was prior to Angular 1.5 which introduce Tranclusion.

link: function(scope, element) {
      if (!element.find('ng-transclude').children().length) {
        element.find('button').append('<b style="color: red">Button1</b>');
      }
    }

Plunker

Coequal answered 19/7, 2016 at 12:31 Comment(1)
That would work for my button directive, but my toolbar could include any html content, including 3rd party directives whose code I do not control and might not have implemented this fallback.Grivet

© 2022 - 2024 — McMap. All rights reserved.