Comment puis-je ajouter dynamiquement une directive dans AngularJS?

212

J'ai une version très résumée de ce que je fais qui fait passer le problème.

J'ai un simple directive. Chaque fois que vous cliquez sur un élément, il en ajoute un autre. Cependant, il doit d'abord être compilé afin de le rendre correctement.

Mes recherches m'ont conduit à $compile. Mais tous les exemples utilisent une structure compliquée que je ne sais pas vraiment comment appliquer ici.

Les violons sont ici: http://jsfiddle.net/paulocoelho/fBjbP/1/

Et le JS est là:

var module = angular.module('testApp', [])
    .directive('test', function () {
    return {
        restrict: 'E',
        template: '<p>{{text}}</p>',
        scope: {
            text: '@text'
        },
        link:function(scope,element){
            $( element ).click(function(){
                // TODO: This does not do what it's supposed to :(
                $(this).parent().append("<test text='n'></test>");
            });
        }
    };
});

Solution de Josh David Miller: http://jsfiddle.net/paulocoelho/fBjbP/2/

PCoelho
la source

Réponses:

259

Vous avez beaucoup de jQuery inutiles là-dedans, mais le service $ compile est en fait super simple dans ce cas:

.directive( 'test', function ( $compile ) {
  return {
    restrict: 'E',
    scope: { text: '@' },
    template: '<p ng-click="add()">{{text}}</p>',
    controller: function ( $scope, $element ) {
      $scope.add = function () {
        var el = $compile( "<test text='n'></test>" )( $scope );
        $element.parent().append( el );
      };
    }
  };
});

Vous remarquerez que j'ai également refactorisé votre directive afin de suivre quelques bonnes pratiques. Faites-moi savoir si vous avez des questions à ce sujet.

Josh David Miller
la source
34
Impressionnant. Ça marche. Voir, ces exemples simples et basiques sont ceux qui devraient être montrés dans les documents des angulaires. Ils commencent par des exemples compliqués.
PCoelho
1
Merci, Josh, c'était vraiment utile. J'ai créé un outil dans Plnkr que nous utilisons dans un nouveau CoderDojo pour aider les enfants à apprendre à coder, et je l'ai juste étendu pour que je puisse maintenant utiliser des directives de démarrage angulaire comme le sélecteur de date, l'alerte, les onglets, etc. Apparemment, j'ai créé quelque chose et pour l'instant, cela ne fonctionne que dans Chrome: embed.plnkr.co/WI16H7Rsa5adejXSmyNj/preview
JoshGough
3
Josh - quel est le moyen le plus simple d'accomplir cela sans utiliser $compile? Merci pour votre réponse d'ailleurs!
double fil
3
@doubleswirve Dans ce cas, il serait beaucoup plus facile d'utiliser simplement ngRepeat. :-) Mais je suppose que vous voulez dire l'ajout dynamique de nouvelles directives à la page, auquel cas la réponse est non - il n'y a pas de moyen plus simple car le $compileservice est ce qui relie les directives et les connecte au cycle d'événements. Il n'y a aucun moyen de contourner $compileune telle situation, mais dans la plupart des cas une autre directive comme ngRepeat peut accomplir le même travail (donc ngRepeat fait la compilation pour nous). Avez-vous un cas d'utilisation spécifique?
Josh David Miller
2
La compilation ne devrait-elle pas se produire au stade de la pré-liaison? Je pense que le contrôleur ne devrait contenir que du code non DOM, testable par unité, mais je suis nouveau dans le concept de lien / contrôleur, donc je ne suis pas sûr de moi. En outre, une alternative de base est ng-include + partial + ng-controller car elle agira comme une directive avec une portée héritée .
Marcus Rådell
77

En plus de l'exemple parfait de Riceball LEE d'ajouter une nouvelle directive élément

newElement = $compile("<div my-directive='n'></div>")($scope)
$element.parent().append(newElement)

Ajout d'une nouvelle directive attribut à un élément existant peut se faire de cette façon:

Supposons que vous souhaitiez ajouter à la volée my-directivel' spanélément.

template: '<div>Hello <span>World</span></div>'

link: ($scope, $element, $attrs) ->

  span = $element.find('span').clone()
  span.attr('my-directive', 'my-directive')
  span = $compile(span)($scope)
  $element.find('span').replaceWith span

J'espère que cela pourra aider.

impasse
la source
3
N'oubliez pas de supprimer la directive d'origine afin d'éviter une erreur de taille maximale de la pile d'appels.
SRachamim
Bonjour, pourriez-vous s'il vous plaît fournir des idées sur ma nouvelle API proposée pour faire de l'ajout de directives par programme un processus plus simple? github.com/angular/angular.js/issues/6950 Merci!
trusktr
Je souhaite qu'en 2015, nous n'aurions pas de limites de taille de pile d'appels. :(
psycho brm
3
L' Maximum call stack size exceedederreur se produit toujours en raison d'une récursion infinie. Je n'ai jamais vu d'instance où l'augmentation de la taille de la pile le résoudrait.
Gunchars le
Problème similaire auquel je suis confronté, pouvez-vous m'aider ici stackoverflow.com/questions/38821980/…
pandu das
45

L'ajout dynamique de directives sur angularjs a deux styles:

Ajouter une directive angularjs dans une autre directive

  • insertion d'un nouvel élément (directive)
  • insertion d'un nouvel attribut (directive) à l'élément

insertion d'un nouvel élément (directive)

c'est simple. Et vous pouvez utiliser dans "lien" ou "compiler".

var newElement = $compile( "<div my-diretive='n'></div>" )( $scope );
$element.parent().append( newElement );

insertion d'un nouvel attribut à l'élément

C'est dur et ça me fait mal à la tête en deux jours.

L'utilisation de "$ compile" soulèvera une erreur récursive critique !! Peut-être qu'il devrait ignorer la directive actuelle lors de la recompilation de l'élément.

$element.$set("myDirective", "expression");
var newElement = $compile( $element )( $scope ); // critical recursive error.
var newElement = angular.copy(element);          // the same error too.
$element.replaceWith( newElement );

Je dois donc trouver un moyen d'appeler la fonction directive "link". Il est très difficile d'obtenir les méthodes utiles qui sont cachées profondément à l'intérieur des fermetures.

compile: (tElement, tAttrs, transclude) ->
   links = []
   myDirectiveLink = $injector.get('myDirective'+'Directive')[0] #this is the way
   links.push myDirectiveLink
   myAnotherDirectiveLink = ($scope, $element, attrs) ->
       #....
   links.push myAnotherDirectiveLink
   return (scope, elm, attrs, ctrl) ->
       for link in links
           link(scope, elm, attrs, ctrl)       

Maintenant, ça marche bien.

Riceball LEE
la source
1
J'adorerais voir une démonstration de l'insertion d'un nouvel attribut à l'élément, dans vanilla JS si possible - il me manque quelque chose ...
Patrick
le véritable exemple d'insertion d'un nouvel attribut à l'élément est ici (voir mon github): github.com/snowyu/angular-reactable/blob/master/src/…
Riceball LEE
1
N'aide pas honnêtement. C'est ainsi que j'ai fini par résoudre mon problème: stackoverflow.com/a/20137542/1455709
Patrick
Oui, ce cas est l'insertion d'une directive d'attribut dans une autre directive, pas l'élément d'insertion dans le modèle.
Riceball LEE
Quel est le raisonnement derrière le faire en dehors du modèle?
Patrick
9
function addAttr(scope, el, attrName, attrValue) {
  el.replaceWith($compile(el.clone().attr(attrName, attrValue))(scope));
}
user1212212
la source
5

La réponse acceptée par Josh David Miller fonctionne très bien si vous essayez d'ajouter dynamiquement une directive qui utilise un inline template. Cependant, si votre directive profite de templateUrlsa réponse, cela ne fonctionnera pas. Voici ce qui a fonctionné pour moi:

.directive('helperModal', [, "$compile", "$timeout", function ($compile, $timeout) {
    return {
        restrict: 'E',
        replace: true,
        scope: {}, 
        templateUrl: "app/views/modal.html",
        link: function (scope, element, attrs) {
            scope.modalTitle = attrs.modaltitle;
            scope.modalContentDirective = attrs.modalcontentdirective;
        },
        controller: function ($scope, $element, $attrs) {
            if ($attrs.modalcontentdirective != undefined && $attrs.modalcontentdirective != '') {
                var el = $compile($attrs.modalcontentdirective)($scope);
                $timeout(function () {
                    $scope.$digest();
                    $element.find('.modal-body').append(el);
                }, 0);
            }
        }
    }
}]);
ferics2
la source
5

Josh David Miller a raison.

PCoelho, au cas où vous vous demandez ce qui se $compilepasse dans les coulisses et comment la sortie HTML est générée à partir de la directive, veuillez jeter un œil ci-dessous

Le $compileservice compile le fragment de HTML ( "< test text='n' >< / test >") qui inclut la directive ("test" comme élément) et produit une fonction. Cette fonction peut ensuite être exécutée avec une portée pour obtenir la "sortie HTML d'une directive".

var compileFunction = $compile("< test text='n' > < / test >");
var HtmlOutputFromDirective = compileFunction($scope);

Plus de détails avec des exemples de code complets ici: http://www.learn-angularjs-apps-projects.com/AngularJs/dynamically-add-directives-in-angularjs

Danial Lokman
la source
4

Inspiré de la plupart des réponses précédentes, j'ai trouvé la directive "stroman" suivante qui se remplacera par toutes les autres directives.

app.directive('stroman', function($compile) {
  return {
    link: function(scope, el, attrName) {
      var newElem = angular.element('<div></div>');
      // Copying all of the attributes
      for (let prop in attrName.$attr) {
        newElem.attr(prop, attrName[prop]);
      }
      el.replaceWith($compile(newElem)(scope)); // Replacing
    }
  };
});

Important: enregistrez les directives que vous souhaitez utiliser restrict: 'C'. Comme ça:

app.directive('my-directive', function() {
  return {
    restrict: 'C',
    template: 'Hi there',
  };
});

Vous pouvez utiliser comme ceci:

<stroman class="my-directive other-class" randomProperty="8"></stroman>

Pour l'obtenir:

<div class="my-directive other-class" randomProperty="8">Hi there</div>

Protip. Si vous ne voulez pas utiliser de directives basées sur des classes, vous pouvez changer '<div></div>'quelque chose que vous aimez. Par exemple, avoir un attribut fixe qui contient le nom de la directive souhaitée au lieu de class.

Gábor Imre
la source
Problème similaire auquel je fais face, pouvez-vous m'aider ici stackoverflow.com/questions/38821980/…
pandu das
OMG. il a fallu 2 jours pour trouver cette compilation $ ... merci les amis .. ça marche mieux ... AJS vous rockez ....
Srinivasan