wake-up-neo.com

Illegale Verwendung der Direktive ngTransclude in der Vorlage

Ich habe zwei Anweisungen 

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) {
        }
    }
});

Und das ist meine Ansicht:

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

Fehler: [ngTransclude: Orphan] Unzulässige Verwendung der Direktive ngTransclude in der Vorlage! Keine übergeordnete Direktive, die eine Transklusion erfordert. Element: <div class="ng-scope" ng-transclude="">

Ich weiß, dass Panel1 keine praktische Richtlinie ist. Aber in meiner realen Anwendung stoße ich auch auf dieses Problem.

Ich sehe einige Erklärungen auf http://docs.angularjs.org/error/ngTransclude:Orphan . Aber ich weiß nicht, warum ich diesen Fehler hier habe und wie ich ihn beheben kann.

EDIT Ich habe eine jsfiddle - Seite erstellt. Danke im Voraus.

EDIT

I my real app panel1 does something like this:

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

ergebnis =>

    <div>
    <div class="x"><input type="text></div>
    <div class="x"><input type="text></div>
<!--other elements or directive wrapped in div -->
    </div>
22
Alborz

Der Grund dafür ist, dass das DOM nach dem Laden vollständig ist. In diesem Fall wird das DOM durchlaufen und alle Anweisungen in die Vorlage before konvertiert, die die Compile- und Link-Funktion aufruft. 

Dies bedeutet, dass beim Aufruf von $compile(clone.children()[0])(scope) der clone.children()[0], der Ihr <panel> ist, in diesem Fall ist bereits transformiert von angle . clone.children() wird bereits zu: 

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

(Das Panel-Element wurde entfernt und ersetzt).

Es ist dasselbe, wenn Sie ein normales div mit ng-transclude kompilieren. Wenn Sie ein normales div mit ng-transclude kompilieren, löst winklig eine Ausnahme aus, wie in den Dokumenten angegeben:

Dieser Fehler tritt häufig auf, wenn Sie vergessen haben, die Transkludierung festzulegen: In einigen Direktive-Definitionen true und dann ngTransclude in der Vorlage der Direktive.

DEMO (Überprüfen Sie die Konsole auf Ausgabe)

Selbst wenn Sie replace:false so einstellen, dass <panel> beibehalten wird, wird das transformierte Element manchmal wie folgt angezeigt: 

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

was auch problematisch ist, weil der ng-transclude doppelt vorhanden ist

DEMO

Um zu vermeiden, dass kollidiert mit dem Winkelkompilierungsprozess, empfehle ich, den inneren HTML-Code von <panel1> als template- oder templateUrl -Eigenschaft festzulegen

Ihr HTML:

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

        </panel1>
    </div>

Ihre JS:

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

            }
        });

Wie Sie sehen, ist dieser Code sauberer, da wir uns nicht mit dem manuellen Ausschluss des Elements befassen müssen.

DEMO

Aktualisiert mit einer Lösung zum dynamischen Hinzufügen von Elementen ohne Verwendung von template oder 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

Wenn Sie es auf der HTML-Seite ablegen möchten, stellen Sie sicher, dass Sie es nicht erneut kompilieren:

DEMO

Wenn Sie für jedes Kind ein Div hinzufügen müssen. Verwenden Sie einfach den 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 (Möglicherweise müssen Sie die Vorlage an Ihre Bedürfnisse anpassen, div entfernen oder weitere Divs hinzufügen.)

Lösung basierend auf der aktualisierten Frage des OP:

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

36
Khanh TO

Sie machen ein paar Dinge falsch in Ihrem Code. Ich werde versuchen, sie aufzulisten:

Erstens sollten Sie, da Sie den Winkel 1.2.6 verwenden, den Transclude (Ihre Linkerfunktion) nicht mehr als Parameter für die Kompilierfunktion verwenden. Dies ist veraltet und sollte nun als 5. Parameter an Ihre Link-Funktion übergeben werden:

compile: function (element, attr) {
  return function (scope, element, attr, ctrl, linker) {
  ....};

Das verursacht nicht das bestimmte Problem, das Sie sehen, aber es ist eine gute Praxis, die veraltete Syntax nicht mehr zu verwenden.

Das eigentliche Problem besteht darin, wie Sie Ihre Transclude-Funktion in der panel1-Direktive anwenden:

parent.prepend($compile(clone.children()[0])(scope));

Bevor ich näher auf das eingehen kann, was falsch ist, lassen Sie uns kurz nachlesen, wie Transclude funktioniert.

Immer wenn eine Direktive die Transklusion verwendet, wird der transkludierte Inhalt aus dem Dom entfernt. Die kompilierten Inhalte sind jedoch über eine Funktion zugänglich, die als fünfter Parameter Ihrer Link-Funktion (allgemein als Transclude-Funktion bezeichnet) übergeben wird. 

Der Schlüssel ist, dass der Inhalt kompiliert ist. Das bedeutet, Sie sollten $ compile nicht für den dom aufrufen, der an Ihr Transclude übergeben wurde.

Wenn Sie versuchen, Ihr transklusives DOM einzufügen, gehen Sie außerdem zum übergeordneten Objekt und versuchen, es dort hinzuzufügen. Normalerweise sollten Direktiven ihre Dom-Manipulation auf ihr eigenes Element und darunter beschränken und nicht versuchen, den übergeordneten Dom zu ändern. Dies kann den Winkel, der das DOM in der Reihenfolge und hierarchisch durchläuft, stark verwirren. 

Wenn Sie davon ausgehen, was Sie zu tun versuchen, können Sie transclude: true anstelle von transclude: 'element' verwenden. Lassen Sie uns den Unterschied erklären:

transclude: 'element' entfernt das Element selbst aus dem DOM und gibt Ihnen das gesamte Element zurück, wenn Sie die Transclude-Funktion aufrufen.

transclude: true entfernt einfach die untergeordneten Elemente des Elements aus dem dom und gibt Ihnen die untergeordneten Elemente zurück, wenn Sie Ihr Transclude aufrufen.

Da es scheint, dass Sie sich nur für die Kinder interessieren, sollten Sie transclude true verwenden (anstatt die Kinder () von Ihrem Klon zu bekommen). Dann können Sie das Element einfach durch seine untergeordneten Elemente ersetzen (gehen Sie also nicht mit dem übergeordneten dom herum).

Schließlich ist es nicht empfehlenswert, den Gültigkeitsbereich der Funktion der überdeckten Funktion außer Kraft zu setzen, es sei denn, Sie haben einen triftigen Grund (der Inhalt sollte in der Regel den ursprünglichen Geltungsbereich beibehalten). Ich würde also vermeiden, den Gültigkeitsbereich zu übergeben, wenn Sie Ihre linker() aufrufen.

Ihre endgültige vereinfachte Direktive sollte ungefähr so ​​aussehen:

   app.directive('panel1', function ($compile) {
     return {
       restrict: "E",
       transclude: true,
       link: function (scope, element, attr, ctrl, linker) {
           linker(function (clone) {
               element.replaceWith(clone);
           });
       }
    }
   });

Ignorieren Sie, was in der vorherigen Antwort zu replace: true und transclude: true gesagt wurde. So funktionieren die Dinge nicht, und Ihre Panel-Direktive ist in Ordnung und sollte wie erwartet funktionieren, solange Sie Ihre panel1-Direktive reparieren.

Hier ist eine Js-Geige der Korrekturen, die ich hoffentlich gemacht habe, wie es erwartet wird.

http://jsfiddle.net/77Spt/3/

EDIT:

Es wurde gefragt, ob Sie den überdeckten Inhalt in ein div einpacken können. Am einfachsten verwenden Sie einfach eine Vorlage wie in Ihrer anderen Direktive (die ID in der Vorlage ist nur, damit Sie sie in der HTML-Datei sehen können, sie dient keinem anderen Zweck):

   app.directive('panel1', function ($compile) {
       return {
           restrict: "E",
           transclude: true,
           replace: true,
           template: "<div id='wrappingDiv' ng-transclude></div>"          
       }
   });

Oder wenn Sie die Transclude-Funktion verwenden möchten (meine persönliche Präferenz):

   app.directive('panel1', function ($compile) {
       return {
           restrict: "E",
           transclude: true,
           replace: true,
           template: "<div id='wrappingDiv'></div>",
           link: function (scope, element, attr, ctrl, linker) {
               linker(function (clone) {
                   element.append(clone);
               });
           }
       }
   });

Ich bevorzuge diese Syntax, weil ng-transclude eine einfache und dumme Anweisung ist, die leicht zu verwechseln ist. Obwohl es in dieser Situation einfach ist, ist das manuelle Hinzufügen des Doms genau dort, wo Sie möchten, die ausfallsichere Methode.

Hier ist die Geige dafür:

http://jsfiddle.net/77Spt/6/

7
dtabuenc

Ich habe es bekommen, weil ich directiveChild in directiveParent als Ergebnis von transclude verschachtelt hatte.

Der Trick war, dass directiveChild versehentlich dieselbe templateUrl wie directiveParent verwendete. 

0
Ben