Recursão em directivas angulares

Há um par de perguntas e respostas de diretivas angulares recursivas populares, todas com uma das seguintes soluções:

  • manualmente incrementalmente ‘compilar’ HTML com base no estado do escopo de tempo de execução
    • exemplo 1 [ stackoverflow ]
    • exemplo 2 [ página angular jsfiddles ]
  • não use uma diretiva, mas um modelo que se refere a si mesmo
    • exemplo 1 [ grupos do google ]

O primeiro tem o problema de não ser possível remover o código compilado anteriormente, a menos que você gerencie de forma compreensível o processo de compilation manual. A segunda abordagem tem o problema de … não ser uma diretiva e perder suas poderosas capacidades, mas, mais urgentemente, ela não pode ser parametrizada da mesma maneira que uma diretiva pode ser; está simplesmente ligado a uma nova instância do controlador.

Eu tenho jogado com manualmente fazendo um angular.bootstrap ou @compile() na function de link, mas isso me deixa com o problema de manter manualmente o controle de elementos para remover e adicionar.

Existe uma boa maneira de ter um padrão recursivo parametrizado que gerencia elementos de adição / remoção para refletir o estado de tempo de execução? Isto é, uma tree com um botão add / delete node e algum campo de input cujo valor é passado pelos nós filhos de um nó. Talvez uma combinação da segunda abordagem com escopos encadeados (mas não tenho ideia de como fazer isso)?

    Inspirado pelas soluções descritas no encadeamento mencionado por @ dnc253, abstraí a funcionalidade de recursion em um serviço .

     module.factory('RecursionHelper', ['$compile', function($compile){ return { /** * Manually compiles the element, fixing the recursion loop. * @param element * @param [link] A post-link function, or an object with function(s) registered via pre and post properties. * @returns An object containing the linking functions. */ compile: function(element, link){ // Normalize the link parameter if(angular.isFunction(link)){ link = { post: link }; } // Break the recursion loop by removing the contents var contents = element.contents().remove(); var compiledContents; return { pre: (link && link.pre) ? link.pre : null, /** * Compiles and re-adds the contents */ post: function(scope, element){ // Compile the contents if(!compiledContents){ compiledContents = $compile(contents); } // Re-add the compiled contents to the element compiledContents(scope, function(clone){ element.append(clone); }); // Call the post-linking function, if any if(link && link.post){ link.post.apply(null, arguments); } } }; } }; }]); 

    Qual é usado da seguinte maneira:

     module.directive("tree", ["RecursionHelper", function(RecursionHelper) { return { restrict: "E", scope: {family: '='}, template: '

    {{ family.name }}

    '+ '
      ' + '
    • ' + '' + '
    • ' + '
    ', compile: function(element) { // Use the compile function from the RecursionHelper, // And return the linking function(s) which it returns return RecursionHelper.compile(element); } }; }]);

    Veja este Plunker para uma demonstração. Eu gosto mais desta solução porque:

    1. Você não precisa de uma diretiva especial que torne o seu html menos limpo.
    2. A lógica de recursion é abstraída no serviço RecursionHelper, portanto, você mantém suas diretivas limpas.

    Adicionar manualmente elementos e compilá-los é definitivamente uma abordagem perfeita. Se você usar ng-repeat, não precisará remover manualmente os elementos.

    Demonstração: http://jsfiddle.net/KNM4q/113/

     .directive('tree', function ($compile) { return { restrict: 'E', terminal: true, scope: { val: '=', parentData:'=' }, link: function (scope, element, attrs) { var template = '{{val.text}}'; template += ''; if (angular.isArray(scope.val.items)) { template += '
    '; } scope.deleteMe = function(index) { if(scope.parentData) { var itemIndex = scope.parentData.indexOf(scope.val); scope.parentData.splice(itemIndex,1); } scope.val = {}; }; var newElement = angular.element(template); $compile(newElement)(scope); element.replaceWith(newElement); } } });

    Eu não sei ao certo se essa solução é encontrada em um dos exemplos que você vinculou ou no mesmo conceito básico, mas eu precisava de uma diretiva recursiva, e encontrei uma solução fácil e excelente .

     module.directive("recursive", function($compile) { return { restrict: "EACM", priority: 100000, compile: function(tElement, tAttr) { var contents = tElement.contents().remove(); var compiledContents; return function(scope, iElement, iAttr) { if(!compiledContents) { compiledContents = $compile(contents); } iElement.append( compiledContents(scope, function(clone) { return clone; })); }; } }; }); module.directive("tree", function() { return { scope: {tree: '='}, template: '

    {{ tree.text }}

    ', compile: function() { return function() { } } }; });​

    Você deve criar a diretiva recursive e envolvê-la no elemento que faz a chamada recursiva.

    A partir do Angular 1.5.x, não são necessários mais truques, o seguinte foi possível. Não há mais necessidade de trabalho sujo!

    Essa descoberta foi um subproduto da minha busca por uma solução melhor / mais limpa para uma diretiva recursiva. Você pode encontrá-lo aqui https://jsfiddle.net/cattails27/5j5au76c/ . Ele suporta até 1.3.x.

     angular.element(document).ready(function() { angular.module('mainApp', []) .controller('mainCtrl', mainCtrl) .directive('recurv', recurveDirective); angular.bootstrap(document, ['mainApp']); function recurveDirective() { return { template: '
    • {{t.sub}}
    ', scope: { tree: '=' }, } } }); function mainCtrl() { this.tree = [{ title: '1', sub: 'coffee', children: [{ title: '2.1', sub: 'mocha' }, { title: '2.2', sub: 'latte', children: [{ title: '2.2.1', sub: 'iced latte' }] }, { title: '2.3', sub: 'expresso' }, ] }, { title: '2', sub: 'milk' }, { title: '3', sub: 'tea', children: [{ title: '3.1', sub: 'green tea', children: [{ title: '3.1.1', sub: 'green coffee', children: [{ title: '3.1.1.1', sub: 'green milk', children: [{ title: '3.1.1.1.1', sub: 'black tea' }] }] }] }] }]; }
      

    Depois de usar várias soluções alternativas por um tempo, repetidamente voltei a esse problema.

    Não estou satisfeito com a solução de serviço, pois ela funciona para diretivas que podem injetar o serviço, mas não funciona para fragments de modelo anônimos.

    Da mesma forma, as soluções que dependem da estrutura de modelo específica, fazendo manipulação de DOM na diretiva, são muito específicas e frágeis.

    Eu tenho o que eu acredito ser uma solução genérica que encapsula a recursion como uma diretiva própria que interfere minimamente com quaisquer outras diretivas e pode ser usada anonimamente.

    Abaixo está uma demonstração com a qual você também pode brincar com o plnkr: http://plnkr.co/edit/MSiwnDFD81HAOXWvQWIM

     var hCollapseDirective = function () { return { link: function (scope, elem, attrs, ctrl) { scope.collapsed = false; scope.$watch('collapse', function (collapsed) { elem.toggleClass('collapse', !!collapsed); }); }, scope: {}, templateUrl: 'collapse.html', transclude: true } } var hRecursiveDirective = function ($compile) { return { link: function (scope, elem, attrs, ctrl) { ctrl.transclude(scope, function (content) { elem.after(content); }); }, controller: function ($element, $transclude) { var parent = $element.parent().controller('hRecursive'); this.transclude = angular.isObject(parent) ? parent.transclude : $transclude; }, priority: 500, // ngInclude < hRecursive < ngIf < ngRepeat < ngSwitch require: 'hRecursive', terminal: true, transclude: 'element', $$tlb: true // Hack: allow multiple transclusion (ngRepeat and ngIf) } } angular.module('h', []) .directive('hCollapse', hCollapseDirective) .directive('hRecursive', hRecursiveDirective) 
     /* Demo CSS */ * { box-sizing: border-box } html { line-height: 1.4em } .task h4, .task h5 { margin: 0 } .task { background-color: white } .task.collapse { max-height: 1.4em; overflow: hidden; } .task.collapse h4::after { content: '...'; } .task-list { padding: 0; list-style: none; } /* Collapse directive */ .h-collapse-expander { background: inherit; position: absolute; left: .5px; padding: 0 .2em; } .h-collapse-expander::before { content: '•'; } .h-collapse-item { border-left: 1px dotted black; padding-left: .5em; } .h-collapse-wrapper { background: inherit; padding-left: .5em; position: relative; } 
     < !DOCTYPE html>           

    Task Application

    This is an AngularJS application that demonstrates a generalized recursive templating directive. Use it to quickly produce recursive structures in templates.

    The recursive directive was developed in order to avoid the need for recursive structures to be given their own templates and be explicitly self-referential, as would be required with ngInclude. Owing to its high priority, it should also be possible to use it for recursive directives (directives that have templates which include the directive) that would otherwise send the compiler into infinite recursion.

    The directive can be used alongside ng-if and ng-repeat to create recursive structures without the need for additional container elements.

    Since the directive does not request a scope (either isolated or not) it should not impair reasoning about scope visibility, which continues to behave as the template suggests.

    Try playing around with the demonstration, below, where the input at the top provides a way to modify a scope attribute. Observe how the value is visible at all levels.

    The collapse directive is included to further demonstrate that the recursion can co-exist with other transclusions (not just ngIf, et al) and that sibling directives are included on the recursive due to the recursion using whole 'element' transclusion.

    Tasks

    • {{task.name}}

      Volunteers
      • {{who}}
      • {{you}} (you)

    Agora que o Angular 2.0 está na pré-visualização, eu acho que é ok adicionar uma alternativa Angular 2.0 na mixagem. Pelo menos isso irá beneficiar as pessoas mais tarde:

    O conceito-chave é construir um modelo recursivo com uma referência própria:

     
    • {{ dir.name }}
        {{file}}

    Em seguida, você vincula um object de tree ao modelo e observa a recursion cuidar do resto. Aqui está um exemplo completo: http://www.syntaxsuccess.com/viewarticle/recursive-treeview-in-angular-2.0

    Existe uma solução realmente simples para isso que não requer diretivas.

    Bem, nesse sentido, talvez nem seja uma solução do problema original se você assumir que precisa de diretivas, mas é uma solução se você quiser uma estrutura de GUI recursiva com subestruturas parametrizadas da GUI. Qual é provavelmente o que você quer.

    A solução é baseada apenas em ng-controller, ng-init e ng-include. Basta fazer isso da seguinte maneira, suponha que seu controlador seja chamado “MyController”, seu modelo está localizado em myTemplate.html e que você tenha uma function de boot em seu controlador chamada init que recebe o argumento A, B e C, tornando possível parametrize seu controlador. Então a solução é a seguinte:

    myTemplate.htlm:

     
    Hello

    Descobri por simples concordância que esse tipo de estrutura pode se tornar recursiva, como você gosta, na angulação da baunilha. Basta seguir este padrão de design e você pode usar estruturas de interface do usuário recursivas sem qualquer compilation avançada etc.

    Dentro de seu controlador:

     $scope.init = function(A, B, C) { // Do something with A, B, C $scope.D = A + B; // D can be passed on to other controllers in myTemplate.html } 

    A única desvantagem que posso ver é a syntax desajeitada que você tem que suportar.

    Você pode usar o injetor de recursion angular para isso: https://github.com/knyga/angular-recursion-injector

    Permite que você faça aninhamento ilimitado com condicionamento. Recompila somente se necessário e compila apenas elementos corretos. Nenhuma mágica no código.

     
    {{name}}

    Uma das coisas que permite que ele funcione mais rápido e mais simples, então as outras soluções são o sufixo “–recursion”.

    Acabei criando um conjunto de diretivas básicas para recursion.

    IMO É muito mais básico do que a solução encontrada aqui, e tão flexível se não mais, por isso não somos obrigados a usar estruturas UL / LI etc … Mas, obviamente, aqueles fazem sentido usar, no entanto, as directivas desconhecem esta facto…

    Um exemplo super simples seria:

     
    • {{ node.name }}

      A implementação de ‘dx-start-with’ e ‘dx-connect’ é encontrada em: https://github.com/dotJEM/angular-tree

      Isso significa que você não precisa criar 8 diretivas se precisar de 8 layouts diferentes.

      Para criar uma visão em tree em cima de onde você pode adicionar ou excluir nós seria bastante simples. Como em: http://codepen.io/anon/pen/BjXGbY?editors=1010

       angular .module('demo', ['dotjem.angular.tree']) .controller('AppController', function($window) { this.rootNode = { name: 'root node', children: [{ name: 'child' }] }; this.addNode = function(parent) { var name = $window.prompt("Node name: ", "node name here"); parent.children = parent.children || []; parent.children.push({ name: name }); } this.removeNode = function(parent, child) { var index = parent.children.indexOf(child); if (index > -1) { parent.children.splice(index, 1); } } }); 
       
      HELLO TREE
      • {{ node.name }}