Czy dyrektywa kątowa może przekazywać argumenty do funkcji w wyrażeniach określonych w atrybutach dyrektywy?

160

Mam dyrektywę formularza, która używa określonego callbackatrybutu z izolowanym zakresem:

scope: { callback: '&' }

Znajduje się wewnątrz an, ng-repeatwięc wyrażenie, które przekazuję, zawiera idobiekt jako argument funkcji zwrotnej:

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

Kiedy skończyłem pracę z dyrektywą, wywołuje $scope.callback()ona funkcję kontrolera. W większości przypadków jest to w porządku i to wszystko, co chcę zrobić, ale czasami chciałbym dodać kolejny argument z directivesamego wnętrza .

Czy istnieje wyrażenie kątowe, które pozwoliłoby na to $scope.callback(arg2):, skutkując callbackwywołaniem z arguments = [item.id, arg2]?

Jeśli nie, jaki jest najlepszy sposób na zrobienie tego?

Odkryłem, że to działa:

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

Z

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

i wywołanie dyrektywy

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

Ale nie sądzę, że jest to szczególnie schludne i wymaga umieszczenia dodatkowych rzeczy w izolowanym zakresie.

Czy jest lepszy sposób?

Plunker zabaw tutaj (otwórz konsolę).

Ed Hinchliffe
źródło
Atrybut nazwany „callback =” wprowadza w błąd. To jest naprawdę ocena wywołania zwrotnego, a nie samo wywołanie zwrotne.
Dmitri Zaitsev
@DmitriZaitsev to wyrażenie kątowe wywołania zwrotnego, które będzie obliczane na funkcję JavaScript. Myślę, że jest dość oczywiste, że nie jest to sama w sobie funkcja JavaScript. To tylko preferencja, ale wolałbym, aby wszystkie moje atrybuty nie były dodawane do sufiksu „-expression”. Jest to zgodne z ngAPI, na przykład ng-click="someFunction()"jest wyrażeniem, którego wynikiem jest wykonanie funkcji.
Ed Hinchliffe
Nigdy nie widziałem wyrażenia Angulara zwanego „callback”. Jest to zawsze funkcja, którą przekazujesz, aby została wywołana, skąd nazwa. W swoim przykładzie używasz nawet funkcji o nazwie „callback”, aby uczynić rzeczy jeszcze bardziej zagmatwanymi.
Dmitri Zaitsev
Nie jestem pewien, czy jesteś zdezorientowany, czy ja. W moim przykładzie $scope.callbackjest ustawiana przez callback="someFunction"atrybut i scope: { callback: '=' }właściwość obiektu definicji dyrektywy. $scope.callback jest funkcją, która zostanie wywołana w późniejszym terminie. Rzeczywista wartość atrybutu jest oczywiście ciągiem znaków - tak jest zawsze w przypadku HTML.
Ed Hinchliffe
Nazywasz tak samo atrybut i funkcję - „callback”. To przepis na zamieszanie. Naprawdę łatwo było tego uniknąć.
Dmitri Zaitsev

Odpowiedzi:

215

Jeśli zadeklarujesz swoje wywołanie zwrotne, jak wspomniano w @ lex82 like

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

Możesz wywołać metodę wywołania zwrotnego w zakresie dyrektywy z mapą obiektów i poprawnie wykona powiązanie. Lubić

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

bez wymagania $ parse. Zobacz moje skrzypce (dziennik konsoli) http://jsfiddle.net/k7czc/2/

Aktualizacja : w dokumentacji jest mały przykład :

& lub & attr - zapewnia sposób wykonania wyrażenia w kontekście zakresu nadrzędnego. Jeśli nie określono nazwy atrybutu, zakłada się, że nazwa atrybutu jest taka sama, jak nazwa lokalna. Podana i widżetowa definicja zakresu: {localFn: '& myAttr'}, a następnie właściwość zakresu izolowania localFn wskaże opakowanie funkcji dla wyrażenia count = count + value. Często pożądane jest przekazywanie danych z zakresu izolowanego za pośrednictwem wyrażenia i do zakresu nadrzędnego. Można to zrobić, przekazując mapę lokalnych nazw zmiennych i wartości do opakowania wyrażenia fn. Na przykład, jeśli wyrażenie to inkrementacja (kwota), możemy określić wartość kwoty, wywołując localFn jako localFn ({amount: 22}).

Chandermani
źródło
4
Bardzo dobrze! Czy jest to gdzieś udokumentowane?
ach
12
Nie sądzę, żeby to było dobre rozwiązanie, ponieważ w definicji dyrektywy czasami nie wiesz, jaki parametr ma przekazać.
OMGPOP
To dobre rozwiązanie i dziękuję za to, ale uważam, że odpowiedź wymaga nieco uwagi. Kim jest Lex82 i o czym wspomniał?
Wtower
Ciekawe podejście. Chociaż co się dzieje, gdy chcesz zezwolić na przekazanie dowolnej funkcji z DOWOLNYM parametrem (lub wieloma)? Nie wiesz nic o funkcji ani jej parametrach i musisz ją wykonać na jakimś zdarzeniu wewnątrz dyrektywy. Jak się do tego zabrać? Na przykład w dyrektywie możesz mieć onchangefunc = 'myCtrlFunc (dynamicVariableHere)'
trainoasis
58

Nie ma nic złego w innych odpowiedziach, ale używam następującej techniki podczas przekazywania funkcji w atrybucie dyrektywy.

Nie używaj nawiasów, jeśli dołączasz dyrektywę do kodu HTML:

<my-directive callback="someFunction" />

Następnie „rozpakuj” funkcję w linku lub kontrolerze dyrektywy. Oto przykład:

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

Krok „rozpakowywania” umożliwia wywołanie funkcji przy użyciu bardziej naturalnej składni. Zapewnia również, że dyrektywa działa poprawnie, nawet gdy jest zagnieżdżona w innych dyrektywach, które mogą przekazać tę funkcję. Jeśli nie rozpakowałeś, to jeśli masz taki scenariusz:

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

Wtedy w swojej wewnętrznej dyrektywie otrzymałeś coś takiego:

callback()()()(data); 

Co by się nie udało w innych scenariuszach zagnieżdżania.

Zaadaptowałem tę technikę z doskonałego artykułu Dana Wahlina na http://weblogs.asp.net/dwahlin/creating-custom-angularjs-directives-part-3-isolate-scope-and-function-parameters

Dodałem krok rozpakowywania, aby wywoływanie funkcji było bardziej naturalne i aby rozwiązać problem zagnieżdżenia, który napotkałem w projekcie.

ItsCosmo
źródło
2
Fajne podejście, ale nie jestem w stanie użyć thiswskaźnika wewnątrz metody wywołania zwrotnego, ponieważ korzysta z zakresu dyrektywy. Używam Typescript i moje wywołanie zwrotne wygląda następująco: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
Uwaga: jeśli masz zagnieżdżone dyrektywy i chcesz propagować wywołanie zwrotne w górę, musisz rozpakować każdą dyrektywę, a nie tylko tę, która wyzwala wywołanie zwrotne.
Episodex
43

W dyrektywie ( myDirective):

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

W szablonie dyrektywy:

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

W źródle:

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

... gdzie myFunctionjest zdefiniowane w kontrolerze.

Zauważ, że paramw dyrektywie szablon wiąże się starannie paramze źródłem i jest ustawiony na item.


Aby wywołać z linkwłaściwości dyrektywy („z jej wnętrza”), użyj bardzo podobnego podejścia:

...
directive.link = function(isolatedScope) {
    isolatedScope.boundFunction({param: "foo"});
};
...
return directive;
Ben
źródło
Mając In source: bound-function = 'myFunction (obj1.param, obj2.param)'> to jak postępować?
Ankit Pandey
15

Tak, jest lepszy sposób: możesz użyć usługi $ parse w swojej dyrektywie, aby ocenić wyrażenie w kontekście zakresu nadrzędnego, jednocześnie wiążąc określone identyfikatory w wyrażeniu z wartościami widocznymi tylko w dyrektywie:

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

Dodaj tę linię do funkcji link dyrektywy, gdzie możesz uzyskać dostęp do atrybutów dyrektywy.

Twój atrybut callback może być wtedy ustawiony tak, jak callback = "callback(item.id, arg2)"ponieważ arg2 jest powiązany z yourSecondArgument przez usługę $ parse wewnątrz dyrektywy. Dyrektywy takie jak ng-clickumożliwiają dostęp do zdarzenia kliknięcia za pośrednictwem $eventidentyfikatora wewnątrz wyrażenia przekazanego do dyrektywy przy użyciu dokładnie tego mechanizmu.

Zauważ, że nie musisz tworzyć callbackelementu członkowskiego zakresu izolowanego za pomocą tego rozwiązania.

lex82
źródło
3
Użycie scope.$parentsprawia, że ​​dyrektywa jest „nieszczelna” - za dużo „wie” o świecie zewnętrznym, czego nie powinien posiadać dobrze zaprojektowany komponent hermetyzowany.
Dmitri Zaitsev
3
Cóż, wie, że ma zakres nadrzędny, ale nie ma dostępu do określonego pola w zakresie, więc myślę, że jest to tolerowane.
lex82
0

Dla mnie zadziałało:

w dyrektywie zadeklaruj to następująco:

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

W szablonie dyrektywy użyj go w następujący sposób:

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

A kiedy używasz dyrektywy, przekaż funkcję w ten sposób:

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

Przekazujesz funkcję przez jej deklarację i jest ona wywoływana z dyrektywy, a parametry są wypełniane.

michal.jakubeczy
źródło