Directive récursive

Réaliser une directive récursive n'est pas chose aisée avec AngularJS. Si on tente de rappeller la même directive dans le template de celle-ci, même en mettant des conditions à l'aide de ng-repeat ou ng-if, on a droit à une erreur de boucle infinie :

RangeError: Maximum call stack size exceeded  

comme on peut le constater sur l'exemple suivant.

Pour parvenir à nos fins, nous allons donc être obligé de modifier la méthode de compilation de la directive. Mais nous allons d'abord découper notre directive en plusieurs parties :

  • root : la racine de notre arbre
  • children : une liste de branches
  • child : une branche qui peut contenir une liste de branche

Ce qui donnera quelquechoses comme ceci :

<tree-root>  
  <tree-children>
    <tree-child>
      <tree-children>
        <tree-child></tree-child>
        <tree-child></tree-child>
        <tree-child></tree-child>
      </tree-children>
    </tree-child>
    <tree-child></tree-child>
    <tree-child></tree-child>
  </tree-children>
</tree-root>  

Nous aurons donc trois directives : tree-root, tree-children et tree-child.

tree-root va simplement initialiser une balise tree-children en lui passant la liste des enfants. tree-children va insérer une balise tree-child pour chaque enfant à l'aide de ngRepeat. Et pour boucler, tree-child va afficher son contenu, mais aussi insérer un tree-children s'il a des enfants.

Pour éviter la boucle infinie lors de la compilation du template de la directive tree-child, nous allons modifier la façon dont elle se compile. Le principe est de supprimer le contenu de la directive récursive avant sa compilation, donc, au moment où on ne possède pas encore les données qui permettront de limiter la récursivité, et de reporter cette action à la phase du postLink, donc, quand les données du scope sont présentes.
Sans cette modification, la compilation de tree-root entraine la compilation de tree-children qui entraine la compilation de tree-child qui entraine la compilation de tree-children qui entraine la compilation de tree-child qui entraine la compilation de tree-children qui entraine la compilation de tree-child qui entraine la compilation de tree-children qui entraine la compilation de tree-child qui entraine la compilation de tree-children qui entraine la compilation de tree-child qui entraine la compilation de tree-children qui entraine la compilation de tree-child qui entraine la compilation de tree-children qui entraine la compilation de tree-child qui entraine la compilation de tree-children qui entraine la compilation de tree-child qui entraine la compilation de tree-children qui entraine la compilation de tree-child qui entraine la compilation de tree-children qui entraine la compilation de tree-child qui entraine la compilation de tree-children qui entraine la compilation de tree-child qui entraine la compilation de tree-children qui entraine la compilation de tree-child qui entraine la compilation de tree-children qui entraine la compilation de tree-child qui entraine la compilation de tree-children qui entraine la compilation de tree-child qui entraine la compilation de tree-children qui entraine la compilation de tree-child et qui plante.
Ce qu'on fait, c'est qu'on supprime le contenu de tree-child. Ainsi, tree-root entraine la compilation de tree-children qui entraine la compilation de tree-child. Et puis plus tard, quand tree-child reçoit un scope et construit son DOM, il compile son contenu retrouvé.

Et voilà le résultat :

Hadrien

Hi, I'm a french Javascript Lead Developer, Web Architect from Toulouse, France. I've worked for 12 years for many projects with YUI, AngularJS, Aurelia.io and now React and React native.

Toulouse, France https://hadrien.eu