Panggil fungsi pengontrol dari direktif tanpa lingkup terisolasi di AngularJS

95

Saya tidak bisa menemukan cara untuk memanggil fungsi pada lingkup induk dari dalam direktif tanpa menggunakan lingkup terisolasi. Saya tahu bahwa jika saya menggunakan lingkup terisolasi saya hanya dapat menggunakan "&" dalam lingkup terisolasi untuk mengakses fungsi pada lingkup induk, tetapi menggunakan lingkup terisolasi ketika tidak diperlukan memiliki konsekuensi. Perhatikan HTML berikut ini:

<button ng-hide="hideButton()" confirm="Are you sure?" confirm-action="doIt()">Do It</button>

Dalam contoh sederhana ini, saya ingin menampilkan dialog konfirmasi JavaScript dan hanya memanggil doIt () jika mereka mengklik "OK" di dialog konfirmasi. Ini sederhana menggunakan ruang lingkup terisolasi. Arahannya akan terlihat seperti ini:

.directive('confirm', function () {
    return {
        restrict: 'A',
        scope: {
            confirm: '@',
            confirmAction: '&'
        },
        link: function (scope, element, attrs) {
            element.bind('click', function (e) {
                if (confirm(scope.confirm)) {
                    scope.confirmAction();
                }
            });
        }
    };
})

Tapi masalahnya, karena saya menggunakan scope terisolir, ng-hide dalam contoh di atas tidak lagi dieksekusi terhadap lingkup induk , melainkan dalam lingkup terisolir (karena menggunakan lingkup terisolir pada sembarang direktif menyebabkan semua arahan pada elemen itu menjadi menggunakan ruang lingkup terisolasi). Berikut adalah jsFiddle dari contoh di atas di mana ng-hide tidak berfungsi. (Perhatikan bahwa di biola ini, tombolnya harus bersembunyi saat Anda mengetik "ya" di kotak masukan.)

Alternatifnya adalah TIDAK menggunakan cakupan yang terisolasi , yang sebenarnya adalah apa yang saya inginkan di sini karena tidak perlu ruang lingkup arahan ini untuk diisolasi. Satu-satunya masalah yang saya miliki adalah, bagaimana cara memanggil metode pada lingkup induk jika saya tidak meneruskannya pada lingkup yang terisolasi ?

Berikut adalah jsfiddle di mana saya TIDAK menggunakan scope terisolasi dan ng-hide berfungsi dengan baik, tetapi, tentu saja, panggilan ke confirmAction () tidak berfungsi, dan saya tidak tahu cara membuatnya berfungsi.

Harap dicatat, jawaban yang benar-benar saya cari adalah bagaimana memanggil fungsi di lingkup luar TANPA menggunakan lingkup terisolasi. Dan saya tidak tertarik untuk membuat dialog konfirmasi ini berfungsi dengan cara lain, karena inti dari pertanyaan ini adalah untuk mencari tahu cara melakukan panggilan ke lingkup luar dan masih dapat membuat perintah lain bekerja melawan lingkup induk.

Atau, saya akan tertarik untuk mendengar solusi yang menggunakan lingkup terisolasi jika arahan lain masih akan bekerja melawan lingkup induk , tetapi saya rasa ini tidak mungkin.

Jim Cooper
sumber
Bagi mereka yang lewat, Dan Wahlin telah menulis artikel yang sangat bagus yang menjelaskan cakupan isolasi dan parameter fungsi: weblogs.asp.net/dwahlin/…
tanguy_k

Jawaban:

117

Karena direktif hanya memanggil fungsi (dan tidak mencoba menyetel nilai pada properti), Anda dapat menggunakan $ eval alih-alih $ parse (dengan cakupan non-terisolasi):

scope.$apply(function() {
    scope.$eval(attrs.confirmAction);
});

Atau lebih baik, cukup gunakan $ apply , yang akan $ eval () menggunakan argumennya terhadap cakupan:

scope.$apply(attrs.confirmAction);

Biola

Mark Rajcok
sumber
Tidak tahu itu, terima kasih untuk itu. Saya selalu berpikir metode $ parse agak terlalu bertele-tele.
Clark Pan
2
bagaimana Anda mengatur parameter pada fungsi itu?
CMCDragonkai
2
@CMCDragonkai, jika fungsi memiliki argumen, Anda dapat menentukannya di HTML confirm-action="doIt(arg1)", dan kemudian menyetel scope.arg1sebelum memanggil $ eval. Namun, mungkin akan lebih bersih jika menggunakan $ parse: stackoverflow.com/a/16200618/215945
Mark Rajcok
Apakah tidak mungkin untuk melakukan scope. $ Eval (attrs.doIt ({arg1: 'blah'}) ;?
CMCDragonkai
3
@CMCDragonkai, tidak, scope.$eval(attrs.confirmAction({arg1: 'blah'})tidak akan berhasil. Anda perlu menggunakan $ parse dengan sintaks itu.
Mark Rajcok
17

Anda ingin menggunakan layanan $ parse di AngularJS.

Jadi untuk contoh Anda:

.directive('confirm', function ($parse) {
    return {
        restrict: 'A',

        // Child scope, not isolated
        scope : true,
        link: function (scope, element, attrs) {
            element.bind('click', function (e) {
                if (confirm(attrs.confirm)) {
                    scope.$apply(function(){

                        // $parse returns a getter function to be 
                        // executed against an object
                        var fn = $parse(attrs.confirmAction);

                        // In our case, we want to execute the statement in 
                        // confirmAction i.e. 'doIt()' against the scope 
                        // which this directive is bound to, because the 
                        // scope is a child scope, not an isolated scope. 
                        // The prototypical inheritance of scopes will mean 
                        // that it will eventually find a 'doIt' function 
                        // bound against a parent's scope.
                        fn(scope, {$event : e});
                    });
                }
            });
        }
    };
});

Ada hal yang harus dilakukan dengan menyetel properti menggunakan $ parse, tetapi saya akan membiarkan Anda menyeberangi jembatan itu ketika Anda sampai di sana.

Clark Pan
sumber
Terima kasih, Clark, inilah yang saya cari. Saya telah mencoba menggunakan $ parse, tetapi gagal meneruskan cakupan sehingga tidak berfungsi untuk saya. Ini sempurna.
Jim Cooper
Saya terkejut menemukan bahwa solusi ini juga berfungsi tanpa ruang lingkup: benar. Pemahaman saya adalah ruang lingkup itu: benar adalah apa yang membuat ruang lingkup direktif secara prototipikal mewarisi dari lingkup induk. Tahu mengapa ini masih berfungsi tanpa itu?
Jim Cooper
2
tidak ada cakupan anak (menghapus scope:true) yang berfungsi karena direktif tidak akan membuat cakupan baru sama sekali, dan dengan demikian scopeproperti yang Anda rujuk dalam linkfungsi tersebut memiliki cakupan yang sama persis dengan cakupan 'induk' sebelumnya.
Clark Pan
$ apply cukup untuk kasus ini (lihat jawaban saya).
Mark Rajcok
1
Untuk apa acara $ itu?
CMCDragonkai
12

Panggil secara eksplisit hideButtonlingkup induk.

Ini biolanya: http://jsfiddle.net/pXej2/5/

Dan inilah HTML yang diperbarui:

<div ng-app="myModule" ng-controller="myController">
    <input ng-model="showIt"></input>
    <button ng-hide="$parent.hideButton()" confirm="Are you sure?" confirm-action="doIt()">Do It</button>
</div>
Jason
sumber
1 untuk solusi yang berfungsi. Saya sebenarnya telah mencoba menggunakan $ parent dan ketika tidak berhasil menyerah. Setelah melihat solusi Anda, saya melihat lebih dekat dan ternyata saya gagal menambahkan $ parent ke parameter yang saya berikan (tidak ditampilkan di biola asli saya). Misalnya, jika saya ingin meneruskan showIt ke fungsi hideButton () saya perlu menggunakan $ parent.hideButton ($ parent.showIt) dan saya kehilangan $ parent kedua. Saya akan menerima jawaban Clark, bagaimanapun, karena solusi itu tidak mengharuskan pengguna direktif saya mengetahui bahwa mereka perlu menambahkan $ parent. Terima kasih atas wawasannya!
Jim Cooper
1

Satu-satunya masalah yang saya miliki adalah, bagaimana cara memanggil metode pada lingkup induk jika saya tidak meneruskannya pada lingkup yang terisolasi?

Berikut adalah jsfiddle di mana saya TIDAK menggunakan scope terisolasi dan ng-hide berfungsi dengan baik, tetapi, tentu saja, panggilan ke confirmAction () tidak berfungsi dan saya tidak tahu cara membuatnya berfungsi.

Pertama, poin kecil: Dalam hal ini lingkup pengontrol luar bukanlah lingkup induk dari lingkup direktif; lingkup controller luar ini adalah ruang lingkup direktif ini. Dengan kata lain, nama variabel yang digunakan dalam direktif akan dicari secara langsung di lingkup pengontrol.

Selanjutnya, penulisan attrs.confirmAction()tidak berfungsi karena attrs.confirmActionuntuk tombol ini,

<button ... confirm-action="doIt()">Do It</button>

adalah string "doIt()", dan Anda tidak dapat memanggil string, mis "doIt()"().

Pertanyaan Anda sebenarnya adalah:

Bagaimana cara memanggil suatu fungsi jika saya memiliki namanya sebagai string?

Di JavaScript, Anda kebanyakan memanggil fungsi menggunakan notasi titik, seperti ini:

some_obj.greet()

Tetapi Anda juga bisa menggunakan notasi array:

some_obj['greet']

Perhatikan bagaimana nilai indeks adalah string . Jadi, dalam arahan Anda, Anda cukup melakukan ini:

  link: function (scope, element, attrs) {

    element.bind('click', function (e) {

      if (confirm(attrs.confirm)) {
        var func_str = attrs.confirmAction;
        var func_name = func_str.substring(0, func_str.indexOf('(')); // Or func_str.match(/[^(]+/)[0];
        func_name = func_name.trim();

        console.log(func_name); // => doIt
        scope[func_name]();
      }
    });
  }
7stud
sumber
1 karena gagasan untuk mem-parse fungsi itu rumit dan membantu saya dengan masalah yang serupa tetapi lain. Terima kasih!
Anmol Saraf