Come posso aggiungere dinamicamente una direttiva in AngularJS?


212

Ho una versione molto ridotta di quello che sto facendo che risolve il problema.

Ho un semplice directive. Ogni volta che fai clic su un elemento, ne aggiunge un altro. Tuttavia, deve essere compilato prima per renderlo correttamente.

La mia ricerca mi ha portato a $compile. Ma tutti gli esempi usano una struttura complicata che non so davvero come applicare qui.

I violini sono qui: http://jsfiddle.net/paulocoelho/fBjbP/1/

E la JS è qui:

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

Soluzione di Josh David Miller: http://jsfiddle.net/paulocoelho/fBjbP/2/

Risposte:


259

Hai un sacco di inutili jQuery lì dentro, ma il servizio $ compile è in realtà super semplice in questo caso:

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

Noterai che ho modificato anche la tua direttiva per seguire alcune buone pratiche. Fammi sapere se hai domande su qualcuno di questi.


34
Eccezionale. Funziona. Vedi, questi esempi semplici e di base sono quelli che dovrebbero essere mostrati nei documenti degli angolari. Iniziano con esempi complicati.
PCoelho

1
Grazie, Josh, questo è stato davvero utile. Ho creato uno strumento in Plnkr che stiamo usando in un nuovo CoderDojo per aiutare i bambini a imparare come programmare, e l'ho appena esteso in modo da poter ora usare le direttive Angular Bootstrap come datepicker, alert, tabs, ecc. Apparentemente ho aggiunto qualcosa e in questo momento funziona solo su Chrome: embed.plnkr.co/WI16H7Rsa5adejXSmyNj/preview
JoshGough,

3
Josh - qual è il modo più semplice per farlo senza usare $compile? Grazie per la tua risposta a proposito!
raddoppia il

3
@doubleswirve In questo caso, sarebbe molto più semplice usare solo ngRepeat. :-) Ma suppongo che intendi aggiungere nuove direttive dinamicamente alla pagina, nel qual caso la risposta è no - non c'è modo più semplice perché il $compileservizio è ciò che dirige i fili e li aggancia nel ciclo degli eventi. Non c'è modo di aggirare $compileuna situazione come questa, ma nella maggior parte dei casi un'altra direttiva come ngRepeat può fare lo stesso lavoro (quindi ngRepeat sta facendo la compilazione per noi). Hai un caso d'uso specifico?
Josh David Miller,

2
La compilazione non dovrebbe avvenire nella fase di prelink? Penso che il controller dovrebbe contenere solo codice non DOM, unit-testable, ma sono nuovo nel concetto di link / controller, quindi non sono sicuro. Inoltre, un'alternativa di base è ng-include + partial + ng-controller poiché fungerà da direttiva con ambito ereditato .
Marcus Rådell,

77

Oltre all'esempio perfetto di Riceball LEE sull'aggiunta di una nuova direttiva elemento

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

L'aggiunta di una nuova direttiva di attributo all'elemento esistente potrebbe essere eseguita in questo modo:

Supponiamo che desideri aggiungere al volo my-directivel' spanelemento.

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

Spero che aiuti.


3
Non dimenticare di rimuovere la direttiva originale al fine di prevenire l'errore di dimensione massima dello stack di chiamate superato.
SRachamim,

Ciao, ti prego di fornire idee sulla mia nuova API proposta per rendere l'aggiunta programmatica di direttive un processo più semplice? github.com/angular/angular.js/issues/6950 Grazie!
trusktr,

Vorrei che nel 2015 non avremmo limiti nella dimensione dello stack di chiamate. :(
psycho brm,

3
L' Maximum call stack size exceedederrore si verifica sempre a causa della ricorsione infinita. Non ho mai visto un'istanza in cui l'aumento della dimensione dello stack lo avrebbe risolto.
Gunchars,

Problema simile sto affrontando, Can you help me qui stackoverflow.com/questions/38821980/...
Pandu das

45

L'aggiunta dinamica di direttive su angularjs ha due stili:

Aggiungi una direttiva angularjs in un'altra direttiva

  • inserimento di un nuovo elemento (direttiva)
  • inserendo un nuovo attributo (direttiva) nell'elemento

inserimento di un nuovo elemento (direttiva)

è semplice. E puoi usare in "link" o "compilare".

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

inserendo un nuovo attributo nell'elemento

È difficile e mi fa venire il mal di testa entro due giorni.

L'uso di "$ compile" genererà un errore ricorsivo critico !! Forse dovrebbe ignorare l'attuale direttiva quando si ricompila l'elemento.

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

Quindi, devo trovare un modo per chiamare la funzione "link" della direttiva. È molto difficile ottenere i metodi utili che sono nascosti profondamente nelle chiusure.

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)       

Ora funziona bene.


1
Mi piacerebbe vedere una demo dell'inserimento di un nuovo attributo nell'elemento, se possibile in JS vaniglia - Mi manca qualcosa ...
Patrick,

il vero esempio di inserimento di un nuovo attributo in element è qui (vedi il mio github): github.com/snowyu/angular-reactable/blob/master/src/…
Riceball LEE

1
Non aiuta onestamente. Questo è come ho finito per risolvere il mio problema però: stackoverflow.com/a/20137542/1455709
Patrick

Sì, questo caso è l'inserimento di una direttiva di attributo in un'altra direttiva, non l'elemento di inserimento nel modello.
Riceball LEE

Qual è il ragionamento alla base di farlo al di fuori del modello?
Patrick,

9
function addAttr(scope, el, attrName, attrValue) {
  el.replaceWith($compile(el.clone().attr(attrName, attrValue))(scope));
}

5

La risposta accettata da Josh David Miller funziona benissimo se stai cercando di aggiungere dinamicamente una direttiva che utilizza un inline template. Tuttavia, se la tua direttiva sfrutta la templateUrlsua risposta non funzionerà. Ecco cosa ha funzionato per me:

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

5

Josh David Miller ha ragione.

PCoelho, nel caso ti stia chiedendo cosa $compilefa dietro le quinte e come sia generato l'output HTML dalla direttiva, dai un'occhiata qui sotto

Il $compileservizio compila il frammento di HTML ( "< test text='n' >< / test >") che include la direttiva ("test" come elemento) e produce una funzione. Questa funzione può quindi essere eseguita con un ambito per ottenere "l'output HTML da una direttiva".

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

Maggiori dettagli con esempi di codice completo qui: http://www.learn-angularjs-apps-projects.com/AngularJs/dynamically-add-directives-in-angularjs


4

Ispirato da molte delle risposte precedenti, ho escogitato la seguente direttiva "stroman" che si sostituirà a qualsiasi altra direttiva.

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

Importante: registra le direttive che desideri utilizzare restrict: 'C'. Come questo:

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

Puoi usare così:

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

Per ottenere questo:

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

Protip. Se non vuoi usare le direttive basate sulle classi, puoi cambiare '<div></div>'qualcosa in quello che ti piace. Ad esempio avere un attributo fisso che contiene il nome della direttiva desiderata anziché class.


Problema simile sto affrontando, Can you help me qui stackoverflow.com/questions/38821980/...
Pandu das

OH MIO DIO. ci sono voluti 2 giorni per trovare questa compilazione $ ... grazie amici .. funziona meglio ... AJS, tu rock ....
Srinivasan,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.