Wie kann ich eine Direktive in AngularJS dynamisch hinzufügen?

212

Ich habe eine sehr heruntergekommene Version von dem, was ich tue, die das Problem vermittelt.

Ich habe eine einfache directive. Wenn Sie auf ein Element klicken, wird ein weiteres hinzugefügt. Es muss jedoch zuerst kompiliert werden, um es korrekt zu rendern.

Meine Forschung führte mich zu $compile. Aber alle Beispiele verwenden eine komplizierte Struktur, die ich hier nicht wirklich anwenden kann.

Geigen sind hier: http://jsfiddle.net/paulocoelho/fBjbP/1/

Und der JS ist da:

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

Lösung von Josh David Miller: http://jsfiddle.net/paulocoelho/fBjbP/2/

PCoelho
quelle

Antworten:

259

Sie haben eine Menge sinnloser jQuery drin, aber der $ compile-Dienst ist in diesem Fall eigentlich super einfach :

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

Sie werden feststellen, dass ich auch Ihre Richtlinie überarbeitet habe, um einige bewährte Methoden zu befolgen. Lassen Sie mich wissen, wenn Sie Fragen zu diesen haben.

Josh David Miller
quelle
34
Genial. Es klappt. Sehen Sie, diese einfachen und grundlegenden Beispiele sollten in den Dokumenten von Angulars gezeigt werden. Sie beginnen mit komplizierten Beispielen.
PCoelho
1
Danke, Josh, das war wirklich nützlich. Ich habe in Plnkr ein Tool erstellt, das wir in einem neuen CoderDojo verwenden, um Kindern das Erlernen des Codierens zu erleichtern. Ich habe es nur so erweitert, dass ich jetzt Angular Bootstrap-Anweisungen wie Datepicker, Alert, Tabs usw. verwenden kann. Anscheinend habe ich etwas geändert und im Moment
funktioniert
3
Josh - was ist ein einfacher Weg, um dies zu erreichen, ohne es zu benutzen $compile? Danke übrigens für deine Antwort!
Doppelwirbel
3
@doubleswirve In diesem Fall wäre es viel einfacher, nur ngRepeat zu verwenden. :-) Aber ich nehme an, Sie meinen, Sie fügen der Seite dynamisch neue Anweisungen hinzu. In diesem Fall lautet die Antwort nein - es gibt keinen einfacheren Weg, da der $compileDienst die Anweisungen verkabelt und sie in den Ereigniszyklus einbindet. $compileIn einer solchen Situation führt kein Weg daran vorbei, aber in den meisten Fällen kann eine andere Direktive wie ngRepeat den gleichen Job ausführen (also übernimmt ngRepeat das Kompilieren für uns). Haben Sie einen bestimmten Anwendungsfall?
Josh David Miller
2
Sollte die Kompilierung nicht in der Prelink-Phase erfolgen? Ich denke, dass der Controller nur Nicht-DOM-Code enthalten sollte, der auf Einheiten getestet werden kann, aber ich bin neu im Link- / Controller-Konzept, daher bin ich mir nicht sicher. Eine grundlegende Alternative ist ng-include + Partial + ng-Controller, da es als Direktive mit geerbtem Gültigkeitsbereich fungiert.
Marcus Rådell
77

Neben dem perfekten Beispiel von Riceball LEE für das Hinzufügen einer neuen Element-Direktive

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

Das Hinzufügen einer neuen Attribut-Direktive zu einem vorhandenen Element kann folgendermaßen erfolgen:

Angenommen, Sie möchten my-directivedas spanElement im laufenden Betrieb hinzufügen .

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

Hoffentlich hilft das.

Deadrunk
quelle
3
Vergessen Sie nicht, die ursprüngliche Direktive zu entfernen, um zu verhindern, dass die maximale Größe des Aufrufstapels den Fehler überschreitet.
SRachamim
Hallo, würden Sie bitte Ideen zu meiner neuen vorgeschlagenen API bereitstellen, um das programmgesteuerte Hinzufügen von Anweisungen zu vereinfachen? github.com/angular/angular.js/issues/6950 Danke!
trusktr
Ich wünschte, wir hätten 2015 keine Grenzen für die Größe des Aufrufstapels. :(
Psycho Brm
3
Der Maximum call stack size exceededFehler tritt immer aufgrund einer unendlichen Rekursion auf. Ich habe noch nie einen Fall gesehen, in dem eine Erhöhung der Stapelgröße das Problem lösen würde.
Gunchars
Ähnliches Problem, mit dem ich konfrontiert bin, können Sie mir hier helfen stackoverflow.com/questions/38821980/…
pandu das
45

Das dynamische Hinzufügen von Direktiven zu anglejs hat zwei Stile:

Fügen Sie eine Angularjs-Direktive in eine andere Direktive ein

  • Einfügen eines neuen Elements (Direktive)
  • Einfügen eines neuen Attributs (Direktive) in das Element

Einfügen eines neuen Elements (Direktive)

Es ist einfach. Und du kannst in "Link" oder "Kompilieren" verwenden.

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

Einfügen eines neuen Attributs in das Element

Es ist schwer und macht mir innerhalb von zwei Tagen Kopfschmerzen.

Die Verwendung von "$ compile" führt zu einem kritischen rekursiven Fehler !! Möglicherweise sollte die aktuelle Anweisung beim erneuten Kompilieren des Elements ignoriert werden.

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

Ich muss also einen Weg finden, die Direktivenfunktion "link" aufzurufen. Es ist sehr schwer, die nützlichen Methoden zu finden, die tief in Verschlüssen verborgen sind.

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)       

Jetzt funktioniert es gut.

Riceball LEE
quelle
1
Würde gerne eine Demo sehen, in der ein neues Attribut in Element eingefügt wird, wenn möglich in Vanilla JS - mir fehlt etwas ...
Patrick
Das eigentliche Beispiel für das Einfügen eines neuen Attributs in ein Element finden Sie hier (siehe mein Github): github.com/snowyu/angular-reactable/blob/master/src/…
Riceball LEE
1
Hilft nicht ehrlich. So habe ich mein Problem gelöst: stackoverflow.com/a/20137542/1455709
Patrick
Ja, in diesem Fall wird eine Attributanweisung in eine andere Anweisung eingefügt, nicht das Einfügen eines Elements in eine Vorlage.
Riceball LEE
Was ist der Grund dafür, es außerhalb der Vorlage zu tun?
Patrick
9
function addAttr(scope, el, attrName, attrValue) {
  el.replaceWith($compile(el.clone().attr(attrName, attrValue))(scope));
}
user1212212
quelle
5

Die akzeptierte Antwort von Josh David Miller funktioniert hervorragend, wenn Sie versuchen, dynamisch eine Direktive hinzuzufügen, die eine Inline verwendet template. Wenn Ihre Direktive jedoch templateUrlseine Antwort ausnutzt, funktioniert dies nicht. Folgendes hat bei mir funktioniert:

.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
quelle
5

Josh David Miller ist richtig.

PCoelho, Falls Sie sich fragen, was $compilesich hinter den Kulissen abspielt und wie die HTML-Ausgabe aus der Direktive generiert wird, schauen Sie bitte unten nach

Der $compileDienst kompiliert das Fragment von HTML ( "< test text='n' >< / test >"), das die Direktive enthält ("test" als Element), und erzeugt eine Funktion. Diese Funktion kann dann mit einem Bereich ausgeführt werden, um die "HTML-Ausgabe von einer Direktive" zu erhalten.

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

Weitere Details mit vollständigen Codebeispielen finden Sie hier: http://www.learn-angularjs-apps-projects.com/AngularJs/dynamically-add-directives-in-angularjs

Danial Lokman
quelle
4

Inspiriert von vielen der vorherigen Antworten habe ich die folgende "stroman" -Richtlinie entwickelt, die sich durch andere Richtlinien ersetzen wird.

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

Wichtig: Registrieren Sie die Anweisungen, mit denen Sie verwenden möchten restrict: 'C'. So was:

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

Sie können wie folgt verwenden:

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

Um dies zu bekommen:

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

Protip. Wenn Sie keine auf Klassen basierenden Anweisungen verwenden möchten, können Sie '<div></div>'zu etwas wechseln , das Ihnen gefällt. ZB haben Sie ein festes Attribut, das den Namen der gewünschten Direktive anstelle von enthält class.

Gábor Imre
quelle
Ähnliches Problem, mit dem ich konfrontiert bin, können Sie mir hier helfen stackoverflow.com/questions/38821980/…
pandu das
OH MEIN GOTT. Es hat 2 Tage gedauert, um dieses $ compile zu finden ... danke Freunde ... es funktioniert am besten ... AJS du rockst ...
Srinivasan