web-dev-qa-db-fra.com

Utilisation illégale de la directive ngTransclude dans le modèle

J'ai deux directives 

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

Et voici mon avis:

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

Erreur: [ngTransclude: Orphan] Utilisation illégale de la directive ngTransclude dans le modèle! Aucune directive parent nécessitant une transclusion trouvée. Elément: <div class="ng-scope" ng-transclude="">

Je sais que le panel1 n'est pas une directive pratique. Mais dans ma vraie application, je rencontre aussi ce problème.

Je vois une explication sur http://docs.angularjs.org/error/ngTransclude:Orphan . Mais je ne sais pas pourquoi j'ai cette erreur ici et comment la résoudre.

EDIT J'ai créé une page jsfiddle . Merci d'avance.

MODIFIER

I my real app panel1 does something like this:

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

résultat =>

    <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

La raison en est que, une fois le DOM chargé, angular traversera le DOM et transformera toutes les directives en son modèle before en appelant la fonction de compilation et de liaison. 

Cela signifie que lorsque vous appelez $compile(clone.children()[0])(scope), le clone.children()[0] qui est votre <panel> dans ce cas est déjà transformé par angular . clone.children() devient déjà: 

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

(l'élément panel a été supprimé et remplacé).

C'est la même chose avec la compilation d'une division normale avec ng-transclude. Lorsque vous compilez un div normal avec ng-transclude, angular lève une exception comme indiqué dans la documentation:

Cette erreur se produit souvent lorsque vous avez oublié de définir transclude: true dans une définition de directive, puis utilisé ngTransclude dans le fichier modèle de la directive.

DEMO (vérifiez la console pour voir la sortie)

Même lorsque vous définissez replace:false pour conserver votre <panel>, vous verrez parfois l'élément transformé comme ceci: 

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

ce qui est également problématique car le ng-transclude est dupliqué

DEMO

Pour éviter en conflit avec le processus de compilation angulaire, il est recommandé de définir le code HTML interne de <panel1> en tant que propriété template ou templateUrl.

Votre HTML:

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

        </panel1>
    </div>

Votre JS:

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

            }
        });

Comme vous pouvez le constater, ce code est plus propre car nous n’avons pas besoin de traiter manuellement la transition de l’élément.

DEMO

Mise à jour avec une solution pour ajouter des éléments de manière dynamique sans utiliser template ou 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

Si vous voulez le mettre sur une page html, assurez-vous de ne pas le recompiler:

DEMO

Si vous devez ajouter une div pour chaque enfant. Utilisez simplement le ng-transclude prêt à l'emploi.

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 (vous devrez peut-être ajuster le modèle à vos besoins, supprimer div ou ajouter plus de divs)

Solution basée sur la question mise à jour de 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

Vous faites quelques erreurs dans votre code. Je vais essayer de les lister:

Premièrement, puisque vous utilisez la version 1.2.6 angulaire, vous ne devez plus utiliser la fonction transclude (votre fonction de lien) en tant que paramètre de la fonction de compilation. Cela a été déconseillé et devrait maintenant être passé en tant que cinquième paramètre de votre fonction de liaison:

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

Cela ne cause pas le problème particulier que vous rencontrez, mais il est recommandé d’arrêter d’utiliser la syntaxe obsolète.

Le vrai problème réside dans la façon dont vous appliquez votre fonction transclude dans la directive panel1:

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

Avant d’expliquer ce qui ne va pas, examinons rapidement le fonctionnement de transclude.

Chaque fois qu'une directive utilise la transclusion, le contenu inclus est supprimé du dom. Mais son contenu compilé est accessible via une fonction passée en tant que cinquième paramètre de votre fonction de liaison (communément appelée fonction de transclude). 

La clé est que le contenu est compilé. Cela signifie que vous ne devriez pas appeler $ compile sur le dom transmis à votre transclude.

De plus, lorsque vous essayez d'insérer votre DOM transclus, vous allez chez le parent et essayez de l'ajouter ici. En règle générale, les directives devraient limiter leur manipulation de dom à leur propre élément et au-dessous, et ne pas essayer de modifier dom dom. Cela peut grandement confondre les angles qui traversent le DOM de manière hiérarchique et hiérarchique. 

À en juger par ce que vous essayez de faire, le moyen le plus simple de le faire consiste à utiliser transclude: true au lieu de transclude: 'element'. Expliquons la différence:

transclude: 'element' enlèvera l'élément du DOM lui-même et vous redonnera la totalité de l'élément lorsque vous appelez la fonction transclude.

transclude: true enlèvera simplement les enfants de l'élément du dom et vous redonnera les enfants lorsque vous appelez votre transclude.

Comme il semble que vous ne vous souciez que des enfants, vous devriez utiliser transclude true (au lieu de récupérer les enfants () de votre clone). Ensuite, vous pouvez simplement remplacer l’élément par ses enfants (par conséquent, ne montez pas et ne jouez pas avec le dom parent).

Enfin, il est déconseillé de remplacer la portée de la fonction incluse sauf si vous avez de bonnes raisons de le faire (le contenu généralement inclus devrait conserver sa portée d'origine). Donc, je voudrais éviter de passer dans le champ d'application lorsque vous appelez votre linker().

Votre dernière directive simplifiée devrait ressembler à quelque chose comme:

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

Ignorez ce qui a été dit dans la réponse précédente à propos de replace: true et transclude: true. Les choses ne fonctionnent pas ainsi, et votre directive panel est correcte et devrait fonctionner comme prévu tant que vous corrigez votre directive panel1.

Voici un js-fiddle des corrections que j'ai apportées, espérons que cela fonctionne comme prévu.

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

MODIFIER:

Il a été demandé si vous pouvez envelopper le contenu inclus dans un div. Le moyen le plus simple est simplement d’utiliser un modèle comme vous le faites dans votre autre directive (l’id dans le modèle est juste pour que vous puissiez le voir dans le code HTML, cela ne sert à rien):

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

Ou si vous souhaitez utiliser la fonction transclude (mes préférences personnelles):

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

La raison pour laquelle je préfère cette syntaxe est que ng-transclude est une directive simple et stupide qui est facilement confondue. Bien que cela soit simple dans cette situation, ajouter manuellement le dom exactement où vous le souhaitez est le moyen le plus sûr de le faire.

Voici le violon pour cela:

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

7
dtabuenc

J'ai eu cela parce que j'avais directiveChild imbriqué dans directiveParent à la suite de transclude.

Le truc était que directiveChild utilisait accidentellement la même templateUrl que directiveParent

0
Ben