Bagaimana saya bisa menambahkan arahan secara dinamis di AngularJS?

212

Saya memiliki versi yang sangat matang dari apa yang saya lakukan yang menyelesaikan masalah.

Saya punya yang sederhana directive. Setiap kali Anda mengklik suatu elemen, elemen itu menambahkan elemen lainnya. Namun, itu perlu dikompilasi terlebih dahulu untuk membuatnya dengan benar.

Penelitian saya menuntun saya $compile. Tetapi semua contoh menggunakan struktur rumit yang saya tidak benar-benar tahu cara mendaftar di sini.

Fiddles ada di sini: http://jsfiddle.net/paulocoelho/fBjbP/1/

Dan JS ada di sini:

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

Solusi oleh Josh David Miller: http://jsfiddle.net/paulocoelho/fBjbP/2/

PCoelho
sumber

Jawaban:

259

Anda memiliki banyak jQuery tidak berguna di sana, tetapi layanan $ compile sebenarnya sangat sederhana dalam hal ini:

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

Anda akan melihat bahwa saya juga mengevaluasi kembali arahan Anda untuk mengikuti beberapa praktik terbaik. Beri tahu saya jika ada pertanyaan tentang hal itu.

Josh David Miller
sumber
34
Luar biasa. Berhasil. Lihat, contoh-contoh sederhana dan mendasar ini adalah yang harus ditunjukkan dalam dokumen angulars. Mereka memulai dengan contoh-contoh rumit.
PCoelho
1
Terima kasih, Josh, ini sangat berguna. Saya membuat alat di Plnkr yang kami gunakan di CoderDojo baru untuk membantu anak-anak belajar kode, dan saya hanya memperpanjangnya sehingga sekarang saya bisa menggunakan arahan Bootstrap Sudut seperti datepicker, waspada, tab, dll. Rupanya saya membuat sesuatu dan sekarang ini hanya berfungsi di Chrome: embed.plnkr.co/WI16H7Rsa5adejXSmyNj/preview
JoshGough
3
Josh - apa cara yang lebih mudah untuk mencapai ini tanpa menggunakan $compile? Omong-omong, terima kasih atas jawaban Anda!
doubleswirve
3
@doubleswirve Dalam hal ini, akan jauh lebih mudah untuk hanya menggunakan ngRepeat. :-) Tapi saya berasumsi maksud Anda menambahkan arahan baru secara dinamis ke halaman, dalam hal ini jawabannya adalah tidak - tidak ada cara yang lebih sederhana karena $compilelayanan adalah apa yang diarahkan oleh kabel dan menghubungkan mereka ke dalam siklus acara. Tidak ada jalan lain $compiledalam situasi seperti ini, tetapi dalam kebanyakan kasus arahan lain seperti ngRepeat dapat menyelesaikan pekerjaan yang sama (jadi ngRepeat melakukan kompilasi untuk kita). Apakah Anda memiliki kasus penggunaan khusus?
Josh David Miller
2
Tidakkah kompilasi terjadi pada tahap prelink? Saya berpikir bahwa pengontrol hanya boleh berisi kode unit-test yang tidak dapat DOM, tetapi saya baru mengenal konsep tautan / pengontrol sehingga saya tidak yakin. Juga, salah satu alternatif dasar adalah ng-include + parsial + ng-controller karena ini akan bertindak sebagai arahan dengan ruang lingkup yang diwarisi .
Marcus Rådell
77

Sebagai tambahan untuk menyempurnakan Riceball, LEE menambahkan elemen-direktif baru

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

Menambahkan atribut-direktif baru ke elemen yang ada dapat dilakukan dengan menggunakan cara ini:

Katakanlah Anda ingin menambahkan on-the-fly my-directiveke spanelemen.

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

Semoga itu bisa membantu.

deadrunk
sumber
3
Jangan lupa untuk menghapus arahan asli untuk mencegah ukuran tumpukan panggilan maksimum melebihi kesalahan.
SRachamim
Hai, bisakah Anda memberikan gagasan tentang API baru yang diusulkan untuk membuat arahan yang terprogram secara terprogram menjadi proses yang lebih sederhana? github.com/angular/angular.js/issues/6950 Terima kasih!
trusktr
Saya berharap pada tahun 2015 kami tidak akan memiliki batasan dalam ukuran panggilan stack. :(
psiko brm
3
The Maximum call stack size exceededKesalahan selalu terjadi karena rekursi tak terbatas. Saya belum pernah melihat contoh di mana peningkatan ukuran tumpukan akan menyelesaikannya.
Gunchars
Masalah serupa yang saya hadapi, Dapatkah Anda membantu saya di sini stackoverflow.com/questions/38821980/…
pandu das
45

Secara dinamis menambahkan arahan pada angularjs memiliki dua gaya:

Tambahkan arahan sudut ke arahan lain

  • memasukkan elemen baru (direktif)
  • menyisipkan atribut baru (direktif) ke elemen

memasukkan elemen baru (direktif)

itu mudah. Dan Anda dapat menggunakan "tautan" atau "kompilasi".

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

menyisipkan atribut baru ke elemen

Sulit, dan membuat saya sakit kepala dalam dua hari.

Menggunakan "$ compile" akan meningkatkan kesalahan rekursif kritis !! Mungkin harus mengabaikan arahan saat ini ketika mengkompilasi ulang elemen.

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

Jadi, saya harus menemukan cara untuk memanggil fungsi "tautan" direktif. Sangat sulit untuk mendapatkan metode yang berguna yang tersembunyi jauh di dalam penutupan.

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)       

Sekarang, ini bekerja dengan baik.

Riceball LEE
sumber
1
Senang melihat demo memasukkan atribut baru ke elemen, di vanilla JS jika memungkinkan - Saya kehilangan sesuatu ...
Patrick
contoh nyata menyisipkan atribut baru ke elemen ada di sini (lihat github saya): github.com/snowyu/angular-reactable/blob/master/src/…
Riceball LEE
1
Tidak membantu dengan jujur. Ini adalah bagaimana saya akhirnya memecahkan masalah saya: stackoverflow.com/a/20137542/1455709
Patrick
Ya, kasus ini adalah memasukkan atribut direktif ke direktif lain, bukan memasukkan elemen dalam template.
Riceball LEE
Apa alasan di balik melakukannya di luar templat?
Patrick
9
function addAttr(scope, el, attrName, attrValue) {
  el.replaceWith($compile(el.clone().attr(attrName, attrValue))(scope));
}
pengguna1212212
sumber
5

Jawaban yang diterima oleh Josh David Miller bekerja sangat baik jika Anda mencoba untuk secara dinamis menambahkan arahan yang menggunakan inline template. Namun jika arahan Anda mengambil manfaat dari templateUrljawabannya tidak akan berhasil. Inilah yang bekerja untuk saya:

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

Josh David Miller benar.

PCoelho, Jika Anda bertanya-tanya apa yang $compileada di balik layar dan bagaimana hasil HTML dihasilkan dari arahan, silakan lihat di bawah

The $compilelayanan mengkompilasi fragmen dari HTML ( "< test text='n' >< / test >") yang mencakup direktif ( "test" sebagai unsur) dan menghasilkan fungsi. Fungsi ini kemudian dapat dieksekusi dengan lingkup untuk mendapatkan "HTML output from a directive".

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

Lebih detail dengan contoh kode lengkap di sini: http://www.learn-angularjs-apps-projects.com/AngularJs/dynamically-add-directives-in-angularjs

Danial Lokman
sumber
4

Terinspirasi dari banyak jawaban sebelumnya saya telah datang dengan arahan "stroman" berikut yang akan menggantikan dirinya dengan arahan lain.

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

Penting: Daftarkan arahan yang ingin Anda gunakan restrict: 'C'. Seperti ini:

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

Anda bisa menggunakan seperti ini:

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

Untuk mendapatkan ini:

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

Protip. Jika Anda tidak ingin menggunakan arahan berdasarkan kelas maka Anda dapat mengubah '<div></div>'sesuatu yang Anda sukai. Misalnya memiliki atribut tetap yang berisi nama arahan yang diinginkan, bukan class.

Gábor Imre
sumber
Masalah serupa yang saya hadapi, Dapatkah Anda membantu saya di sini stackoverflow.com/questions/38821980/…
pandu das
OH TUHAN. butuh 2 hari untuk menemukan $ compile ini ... terima kasih teman-teman .. ini bekerja paling baik ... AJS you rock ....
Srinivasan