Dapatkah arahan sudut melewati argumen ke fungsi dalam ekspresi yang ditentukan dalam atribut direktif?

160

Saya memiliki arahan bentuk yang menggunakan callbackatribut yang ditentukan dengan cakupan terisolasi:

scope: { callback: '&' }

Itu duduk di dalam ng-repeatsehingga ekspresi yang saya berikan termasuk idobjek sebagai argumen untuk fungsi callback:

<directive ng-repeat = "item in stuff" callback = "callback(item.id)"/>

Ketika saya sudah selesai dengan arahan, ia memanggil $scope.callback()dari fungsi pengontrolnya. Untuk sebagian besar kasus, ini baik-baik saja, dan hanya itu yang ingin saya lakukan, tetapi kadang-kadang saya ingin menambahkan argumen lain dari dalam directivedirinya sendiri.

Apakah ada ekspresi sudut yang akan memungkinkan ini $scope.callback(arg2):, mengakibatkan callbackdipanggil dengan arguments = [item.id, arg2]?

Jika tidak, apa cara paling rapi untuk melakukan ini?

Saya menemukan bahwa ini berfungsi:

<directive 
  ng-repeat = "item in stuff" 
  callback = "callback" 
  callback-arg="item.id"/>

Dengan

scope { callback: '=', callbackArg: '=' }

dan panggilan arahan

$scope.callback.apply(null, [$scope.callbackArg].concat([arg2, arg3]) );

Tapi saya tidak berpikir itu sangat rapi dan melibatkan memasukkan barang-barang ekstra dalam ruang lingkup isolasi.

Apakah ada cara yang lebih baik?

Bermain plunker di sini (buka konsol).

Ed Hinchliffe
sumber
Atribut penamaan "callback =" menyesatkan. Ini benar-benar evaluasi panggilan balik, bukan panggilan balik itu sendiri.
Dmitri Zaitsev
@DmitriZaitsev adalah ekspresi sudut callback yang akan mengevaluasi fungsi JavaScript. Saya pikir itu cukup jelas bahwa itu bukan fungsi JavaScript sendiri. Ini hanya preferensi tetapi saya lebih suka tidak harus mencantumkan semua atribut saya dengan "-expression". Ini konsisten dengan ngAPI misalnya ng-click="someFunction()"adalah ekspresi yang mengevaluasi untuk mengeksekusi fungsi.
Ed Hinchliffe
Saya belum pernah melihat ekspresi sudut yang disebut "callback". Itu selalu merupakan fungsi yang Anda lulus untuk dipanggil, dari mana namanya. Anda bahkan menggunakan fungsi yang disebut "callback" dalam contoh Anda, untuk membuat segalanya lebih membingungkan.
Dmitri Zaitsev
Saya tidak yakin apakah Anda bingung atau saya. Dalam contoh saya $scope.callbackdiatur oleh callback="someFunction"atribut dan scope: { callback: '=' }properti objek definisi direktif. $scope.callback adalah fungsi yang dipanggil di kemudian hari. Atribut yang sebenarnya nilai jelas string - yang selalu terjadi dengan HTML.
Ed Hinchliffe
Anda menamai atribut dan fungsinya sama - "callback". Itulah resep untuk kebingungan. Mudah dihindari sebenarnya.
Dmitri Zaitsev

Jawaban:

215

Jika Anda menyatakan panggilan balik seperti yang disebutkan oleh @ lex82 seperti

callback = "callback(item.id, arg2)"

Anda dapat memanggil metode panggilan balik dalam lingkup direktif dengan peta objek dan itu akan melakukan pengikatan dengan benar. Suka

scope.callback({arg2:"some value"});

tanpa membutuhkan $ parse. Lihat biola saya (log konsol) http://jsfiddle.net/k7czc/2/

Pembaruan : Ada contoh kecil dari ini dalam dokumentasi :

& atau & attr - menyediakan cara untuk mengeksekusi ekspresi dalam konteks cakupan induknya. Jika tidak ada nama attr ditentukan maka nama atribut diasumsikan sama dengan nama lokal. Diberikan dan definisi widget dari cakupan: {localFn: '& myAttr'}, lalu pisahkan properti lingkup localFn akan menunjuk ke pembungkus fungsi untuk count = count + value expression. Seringkali diinginkan untuk meneruskan data dari lingkup terisolasi melalui ekspresi dan ke lingkup induk, ini dapat dilakukan dengan meneruskan peta nama variabel lokal dan nilai-nilai ke dalam pembungkus ekspresi fn. Misalnya, jika ekspresi adalah kenaikan (jumlah) maka kita dapat menentukan nilai jumlah dengan memanggil localFn sebagai localFn ({jumlah: 22}).

Chandermani
sumber
4
Sangat bagus! Apakah ini didokumentasikan di mana saja?
ach
12
Saya tidak berpikir itu solusi yang baik karena dalam definisi direktif, kadang-kadang Anda tidak akan tahu apa parameter untuk diteruskan.
OMGPOP
Ini adalah solusi yang bagus dan terima kasih untuk itu, tapi saya yakin jawabannya perlu sedikit pasang. Siapa lex82 dan apa yang dia sebutkan?
Wtower
Pendekatan yang menarik. Meskipun apa yang terjadi ketika Anda ingin membiarkan fungsi apa pun dengan parameter APAPUN (atau beberapa) dilewatkan? Anda tidak tahu apa-apa tentang fungsi atau parameternya dan dan perlu menjalankannya pada beberapa acara di dalam direktif. Bagaimana menghadapi itu? Misalnya pada arahan Anda dapat memiliki onchangefunc = 'myCtrlFunc (dynamicVariableHere)'
trainoasis
58

Tidak ada yang salah dengan jawaban lain, tetapi saya menggunakan teknik berikut ketika meneruskan fungsi dalam atribut direktif.

Tinggalkan tanda kurung ketika menyertakan arahan dalam html Anda:

<my-directive callback="someFunction" />

Lalu "buka" fungsi di tautan atau pengontrol direktif Anda. berikut ini sebuah contoh:

app.directive("myDirective", function() {

    return {
        restrict: "E",
        scope: {
            callback: "&"                              
        },
        template: "<div ng-click='callback(data)'></div>", // call function this way...
        link: function(scope, element, attrs) {
            // unwrap the function
            scope.callback = scope.callback(); 

            scope.data = "data from somewhere";

            element.bind("click",function() {
                scope.$apply(function() {
                    callback(data);                        // ...or this way
                });
            });
        }
    }
}]);    

Langkah "membuka" memungkinkan fungsi dipanggil menggunakan sintaks yang lebih alami. Ini juga memastikan bahwa arahan bekerja dengan baik bahkan ketika bersarang di dalam arahan lain yang mungkin melewati fungsi. Jika Anda tidak melakukan pembungkusan, maka jika Anda memiliki skenario seperti ini:

<outer-directive callback="someFunction" >
    <middle-directive callback="callback" >
        <inner-directive callback="callback" />
    </middle-directive>
</outer-directive>

Maka Anda akan berakhir dengan sesuatu seperti ini di dalam arahan Anda:

callback()()()(data); 

Yang akan gagal dalam skenario bersarang lainnya.

Saya mengadaptasi teknik ini dari artikel yang bagus oleh Dan Wahlin di http://weblogs.asp.net/dwahlin/creating-custom-angularjs-directives-part-3-isolate-scope-and-function-parameters

Saya menambahkan langkah membuka untuk membuat memanggil fungsi lebih alami dan untuk memecahkan masalah bersarang yang saya temui dalam sebuah proyek.

ItsCosmo
sumber
2
Pendekatan yang bagus tapi saya tidak bisa menggunakan thispointer di dalam metode callback, karena menggunakan ruang lingkup direktif. Saya menggunakan naskah dan panggilan balik saya terlihat seperti ini:public validateFirstName(firstName: string, fieldName: string): ng.IPromise<boolean> { var deferred = this.mQService.defer<boolean>(); ... .then(() => deferred.resolve(true)) .catch((msg) => { deferred.reject(false); }); return deferred.promise; }
ndee
1
Perhatikan: jika Anda memiliki arahan bersarang dan ingin menyebarkan callback ke atas, Anda perlu membuka di setiap arahan, tidak hanya arahan yang memicu callback.
Episodex
43

Dalam direktif ( myDirective):

...
directive.scope = {  
    boundFunction: '&',
    model: '=',
};
...
return directive;

Dalam template direktif:

<div 
data-ng-repeat="item in model"  
data-ng-click='boundFunction({param: item})'>
{{item.myValue}}
</div>

Dalam sumber:

<my-directive 
model='myData' 
bound-function='myFunction(param)'>
</my-directive>

... di mana myFunctiondidefinisikan dalam controller.

Perhatikan bahwa paramdalam templat direktif mengikat dengan rapi paramdi sumbernya, dan diatur ke item.


Untuk menelepon dari dalam linkproperti arahan ("dalamnya"), gunakan pendekatan yang sangat mirip:

...
directive.link = function(isolatedScope) {
    isolatedScope.boundFunction({param: "foo"});
};
...
return directive;
Ben
sumber
Saat memiliki sumber: terikat-fungsi = 'myFunction (obj1.param, obj2.param)'> lalu bagaimana cara melanjutkan?
Ankit Pandey
15

Ya, ada cara yang lebih baik: Anda dapat menggunakan layanan $ parse dalam direktif Anda untuk mengevaluasi ekspresi dalam konteks lingkup induk sambil mengikat pengidentifikasi tertentu dalam ekspresi ke nilai yang hanya terlihat di dalam direktif Anda:

$parse(attributes.callback)(scope.$parent, { arg2: yourSecondArgument });

Tambahkan baris ini ke fungsi tautan direktif tempat Anda dapat mengakses atribut direktif.

Atribut callback Anda kemudian dapat diatur seperti callback = "callback(item.id, arg2)"karena arg2 terikat ke yourSecondArgument oleh layanan $ parse di dalam direktif. Arahan seperti ng-clickmemungkinkan Anda mengakses acara klik melalui $eventpengidentifikasi di dalam ekspresi yang diteruskan ke arahan dengan menggunakan mekanisme ini persis.

Perhatikan bahwa Anda tidak harus membuat callbackanggota dari ruang lingkup Anda yang terisolasi dengan solusi ini.

lex82
sumber
3
Menggunakan scope.$parentmembuat arahan "bocor" - ia "tahu" terlalu banyak tentang dunia luar, yang seharusnya tidak diuraikan oleh komponen enkapsulasi yang dirancang dengan baik.
Dmitri Zaitsev
3
Yah, ia tahu bahwa ia memiliki ruang lingkup orang tua tetapi tidak mengakses bidang tertentu dalam ruang lingkup jadi saya pikir ini bisa ditoleransi.
lex82
0

Bagi saya berikut ini berhasil:

dalam direktif mendeklarasikannya seperti ini:

.directive('myDirective', function() {
    return {
        restrict: 'E',
        replace: true,
        scope: {
            myFunction: '=',
        },
        templateUrl: 'myDirective.html'
    };
})  

Dalam direktif templat gunakan dengan cara berikut:

<select ng-change="myFunction(selectedAmount)">

Dan kemudian ketika Anda menggunakan arahan, berikan fungsi seperti ini:

<data-my-directive
    data-my-function="setSelectedAmount">
</data-my-directive>

Anda melewati fungsi dengan deklarasi dan dipanggil dari direktif dan parameter diisi.

michal.jakubeczy
sumber