Uso ilegal da diretiva ngTransclude no modelo

Eu tenho duas diretivas

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: "
", link: function (scope, elem, attrs) { } } });

E esta é a minha opinião:

      

Erro: [ngTransclude: orphan] Uso ilegal da diretiva ngTransclude no modelo! Nenhuma diretiva pai que requer uma transclusão encontrada. Elemento:

Eu sei que o painel1 não é uma diretiva prática. Mas na minha aplicação real também encontro este problema.

Eu vejo algumas explicações em http://docs.angularjs.org/error/ngTransclude:orphan . Mas não sei porque eu tenho esse erro aqui e como resolvê-lo.

EDIT Eu criei uma página jsfiddle . Agradeço antecipadamente.

EDITAR

 I my real app panel1 does something like this:      

resultado =>

  

A razão é quando o DOM é concluído o carregamento, o angular irá percorrer o DOM e transformar todas as diretivas em seu modelo antes de chamar a function de compilation e link.

Isso significa que quando você chama $compile(clone.children()[0])(scope) , o clone.children()[0] que é seu neste caso já é transformado por angular. clone.children() já se torna:

fsafsafasdf

(o elemento do painel foi removido e substituído ).

É o mesmo com você está compilando uma div normal com ng-transclude . Quando você compila uma div normal com ng-transclude , o angular gera uma exceção, como diz nos documentos:

Esse erro geralmente ocorre quando você se esqueceu de definir transclude: true em alguma definição de diretiva e, em seguida, usou ngTransclude no modelo da diretiva.

DEMO (verifique console para ver a saída)

Mesmo quando você configura replace:false para manter seu , algumas vezes você verá o elemento transformado assim:

fsafsafasdf

que também é problemático porque o ng-transclude é duplicado

DEMO

Para evitar conflitos com o processo de compilation angular , recomendo definir o html interno de como modelo ou propriedade templateUrl

Seu HTML:

 

Seu JS:

 app.directive('panel1', function ($compile) { return { restrict: "E", template:"{{firstName}}", } }); 

Como você pode ver, esse código é mais limpo, pois não precisamos lidar com a transclusão do elemento manualmente.

DEMO

Atualizado com uma solução para adicionar elementos dinamicamente sem usar template ou templateUrl:

 app.directive('panel1', function ($compile) { return { restrict: "E", template:"
", link : function(scope,element){ var html = "{{firstName}}"; element.append(html); $compile(element.contents())(scope); } } });

DEMO

Se você quiser colocá-lo na página html, certifique-se de não compilá-lo novamente:

DEMO

Se você precisar adicionar um div por cada criança. Basta usar o out-of-box ng-transclude .

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

DEMO (pode ser necessário ajustar o modelo de acordo com suas necessidades, remover div ou adicionar mais divs)

Solução baseada na pergunta atualizada do OP:

 app.directive('panel1', function ($compile) { return { restrict: "E", replace:true, transclude: true, template:"
", link: function (scope, elem, attrs) { elem.children().wrap("
"); //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

Você está fazendo algumas coisas erradas no seu código. Vou tentar listá-los:

Em primeiro lugar, desde que você está usando o 1.2.6 angular, você não deve mais usar o transclude (sua function de linker) como um parâmetro para a function de compilation. Isso foi reprovado e agora deve ser passado como o quinto parâmetro para sua function de link:

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

Isso não está causando o problema específico que você está vendo, mas é uma boa prática parar de usar a syntax reprovada.

O verdadeiro problema está em como você aplica sua function transclude na diretiva panel1 :

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

Antes de entrar no que está errado, vamos rever rapidamente como funciona o transclude.

Sempre que uma diretiva usa transclusão, o conteúdo transcluído é removido do dom. Mas os conteúdos compilados são acessíveis através de uma function passada como o quinto parâmetro da sua function de link (comumente chamada de function transclude).

A chave é que o conteúdo é compilado . Isto significa que você não deve chamar $ compile no dom passado para o seu transclude.

Além disso, quando você está tentando inserir seu DOM transcluído, você está indo para o pai e tentando adicioná-lo lá. Normalmente, as diretivas devem limitar sua manipulação dom ao seu próprio elemento e abaixo, e não tentar modificar o dom pai. Isso pode confundir muito o ângulo que percorre o DOM em ordem e hierarquicamente.

A julgar pelo que você está tentando fazer, a maneira mais fácil de realizá-lo é usar transclude: true vez de transclude: 'element' . Vamos explicar a diferença:

transclude: 'element' removerá o próprio elemento do DOM e retornará o elemento inteiro de volta quando você chamar a function transclude.

transclude: true apenas removerá os filhos do elemento do dom e devolverá os filhos quando você chamar seu transclude.

Uma vez que parece que você se importa apenas com as crianças, você deve usar transclude verdadeiro (ao invés de pegar as crianças () do seu clone). Então você pode simplesmente replace o elemento com seus filhos (portanto não subir e mexer com o dom pai).

Finalmente, não é uma boa prática replace o escopo da function transcluída, a menos que você tenha uma boa razão para fazê-lo (geralmente o conteúdo transcluído deve manter seu escopo original). Então, eu evitaria passar no escopo quando você chama seu linker() .

Sua diretiva simplificada final deve ser algo como:

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

Ignore o que foi dito na resposta anterior sobre replace: true e transclude: true . Não é assim que as coisas funcionam, e a sua diretiva de painel está bem e deve funcionar como esperado, desde que você corrija sua diretiva panel1 .

Aqui está um js-violino das correções que fiz espero que funcione como você espera.

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

EDITAR:

Foi perguntado se você pode envolver o conteúdo transcluído em uma div. A maneira mais fácil é simplesmente usar um modelo como você faz na sua outra diretiva (o id no template é apenas para que você possa vê-lo no html, ele não serve para nenhum outro propósito):

  app.directive('panel1', function ($compile) { return { restrict: "E", transclude: true, replace: true, template: "
" } });

Ou se você quiser usar a function transclude (minha preferência pessoal):

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

A razão pela qual eu prefiro esta syntax é que o ng-transclude é uma diretiva simples e burra que é facilmente confundida. Embora seja simples nessa situação, adicionar manualmente o dom exatamente onde você quer é a maneira segura de fazê-lo.

Aqui está o violino para isso:

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

Eu tenho isso porque eu tinha directiveChild aninhada em directiveParent como resultado de transclude .

O truque era que directiveChild estava acidentalmente usando o mesmo templateUrl como directiveParent .