Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
643 views
in Technique[技术] by (71.8m points)

angularjs - Illegal use of ngTransclude directive in the template

I have two directive

app.directive('panel1', function ($compile) {
    return {
        restrict: "E",
        transclude: 'element',
        compile: function (element, attr, linker) {
            return function (scope, element, attr) {
                var parent = element.parent();
                linker(scope, function (clone) {
                    parent.prepend($compile( clone.children()[0])(scope));//cause error.
                  //  parent.prepend(clone);// This line remove the error but i want to access the children in my real app.
                });
            };
        }
    }
});

app.directive('panel', function ($compile) {
    return {
        restrict: "E",
        replace: true,
        transclude: true,
        template: "<div ng-transclude ></div>",
        link: function (scope, elem, attrs) {
        }
    }
});

And this is my view :

<panel1>
    <panel>
        <input type="text" ng-model="firstName" />
    </panel>
</panel1>

Error: [ngTransclude:orphan] Illegal use of ngTransclude directive in the template! No parent directive that requires a transclusion found. Element: <div class="ng-scope" ng-transclude="">

I know that panel1 is not a practical directive. But in my real application I encounter this issue too.

I see some explanation on http://docs.angularjs.org/error/ngTransclude:orphan. But I don't know why I have this error here and how to resolve it.

EDIT I have created a jsfiddle page. Thank you in advance.

EDIT

In my real app panel1 does something like this:

    <panel1>
    <input type="text>
    <input type="text>
<!--other elements or directive-->
    </panel1>

result =>

    <div>
    <div class="x"><input type="text></div>
    <div class="x"><input type="text></div>
<!--other elements or directive wrapped in div -->
    </div>
See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

The reason is when the DOM is finished loading, angular will traverse though the DOM and transform all directives into its template before calling the compile and link function.

It means that when you call $compile(clone.children()[0])(scope), the clone.children()[0] which is your <panel> in this case is already transformed by angular. clone.children() already becomes:

<div ng-transclude="">fsafsafasdf</div>

(the panel element has been removed and replaced).

It's the same with you're compiling a normal div with ng-transclude. When you compile a normal div with ng-transclude, angular throws exception as it says in the docs:

This error often occurs when you have forgotten to set transclude: true in some directive definition, and then used ngTransclude in the directive's template.

DEMO (check console to see output)

Even when you set replace:false to retain your <panel>, sometimes you will see the transformed element like this:

<panel class="ng-scope"><div ng-transclude=""><div ng-transclude="" class="ng-scope"><div ng-transclude="" class="ng-scope">fsafsafasdf</div></div></div></panel>

which is also problematic because the ng-transclude is duplicated

DEMO

To avoid conflicting with angular compilation process, I recommend setting the inner html of <panel1> as template or templateUrl property

Your HTML:

<div data-ng-app="app">
        <panel1>

        </panel1>
    </div>

Your JS:

app.directive('panel1', function ($compile) {
            return {
                restrict: "E",
                template:"<panel><input type='text' ng-model='firstName'>{{firstName}}</panel>",

            }
        });

As you can see, this code is cleaner as we don't need to deal with transcluding the element manually.

DEMO

Updated with a solution to add elements dynamically without using template or templateUrl:

app.directive('panel1', function ($compile) {
            return {
                restrict: "E",
                template:"<div></div>",
                link : function(scope,element){
                    var html = "<panel><input type='text' ng-model='firstName'>{{firstName}}</panel>";
                    element.append(html);
                    $compile(element.contents())(scope);
                }
            }
        });

DEMO

If you want to put it on html page, ensure do not compile it again:

DEMO

If you need to add a div per each children. Just use the out-of the box ng-transclude.

app.directive('panel1', function ($compile) {
            return {
                restrict: "E",
                replace:true,
                transclude: true,
                template:"<div><div ng-transclude></div></div>" //you could adjust your template to add more nesting divs or remove 
            }
        });

DEMO (you may need to adjust the template to your needs, remove div or add more divs)

Solution based on OP's updated question:

app.directive('panel1', function ($compile) {
            return {
                restrict: "E",
                replace:true,
                transclude: true,
                template:"<div ng-transclude></div>",
                link: function (scope, elem, attrs) {
                    elem.children().wrap("<div>"); //Don't need to use compile here.
                   //Just wrap the children in a div, you could adjust this logic to add class to div depending on your children
                }
            }
        });

DEMO


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...