Kann eine Winkelanweisung Argumente an Funktionen in Ausdrücken übergeben, die in den Attributen der Anweisung angegeben sind?

160

Ich habe eine Formularanweisung, die ein angegebenes callbackAttribut mit einem isolierten Bereich verwendet:

scope: { callback: '&' }

Es befindet sich in einem, ng-repeatsodass der Ausdruck, den ich übergebe, das iddes Objekts als Argument für die Rückruffunktion enthält:

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

Wenn ich mit der Direktive fertig bin, ruft sie $scope.callback()von ihrer Controller-Funktion auf. In den meisten Fällen ist dies in Ordnung und es ist alles, was ich tun möchte, aber manchmal möchte ich ein weiteres Argument aus dem Inneren heraus hinzufügen directive.

Gibt es einen Winkelausdruck, der dies zulässt: $scope.callback(arg2)was dazu führt, callbackdass mit aufgerufen wird arguments = [item.id, arg2]?

Wenn nicht, wie geht das am besten?

Ich habe festgestellt, dass dies funktioniert:

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

Mit

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

und die Richtlinie aufrufen

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

Aber ich denke nicht, dass es besonders ordentlich ist und es beinhaltet, zusätzliches Zeug in den isolierten Bereich zu stellen.

Gibt es einen besseren Weg?

Plunker Spielplatz hier (Konsole geöffnet haben).

Ed Hinchliffe
quelle
Das Attribut "callback =" führt in die Irre. Es ist wirklich eine Rückrufbewertung, kein Rückruf selbst.
Dmitri Zaitsev
@DmitriZaitsev Es ist ein Rückruf-Winkelausdruck, der als JavaScript-Funktion ausgewertet wird. Ich denke, es ist ziemlich offensichtlich, dass es an sich keine JavaScript-Funktion ist. Es ist nur eine Präferenz, aber ich würde es vorziehen, nicht alle meine Attribute mit "-expression" zu versehen. Dies steht im Einklang mit der ngAPI. Beispielsweise ng-click="someFunction()"handelt es sich um einen Ausdruck, der zur Ausführung einer Funktion ausgewertet wird.
Ed Hinchliffe
Ich habe noch nie einen Winkelausdruck namens "Rückruf" gesehen. Es ist immer eine Funktion, die Sie übergeben, um aufgerufen zu werden, woher der Name. Sie verwenden in Ihrem Beispiel sogar eine Funktion namens "Rückruf", um die Dinge noch verwirrender zu machen.
Dmitri Zaitsev
Ich bin mir nicht sicher, ob du verwirrt bist oder ich. In meinem Beispiel $scope.callbackwird durch das callback="someFunction"Attribut und die scope: { callback: '=' }Eigenschaft des Direktivendefinitionsobjekts festgelegt. $scope.callback ist eine Funktion, die zu einem späteren Zeitpunkt aufgerufen werden soll. Der eigentliche Attribut - Wert ist offensichtlich ein String - , die mit HTML immer der Fall ist.
Ed Hinchliffe
Sie nennen Attribut und Funktion gleich - "Rückruf". Das ist das Rezept für Verwirrung. Leicht zu vermeiden.
Dmitri Zaitsev

Antworten:

215

Wenn Sie Ihren Rückruf wie von @ lex82 erwähnt wie deklarieren

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

Sie können die Rückrufmethode im Direktivenbereich mit Objektzuordnung aufrufen und sie würde die Bindung korrekt ausführen. Mögen

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

ohne dass $ parse erforderlich ist. Siehe meine Geige (Konsolenprotokoll) http://jsfiddle.net/k7czc/2/

Update : In der Dokumentation finden Sie ein kleines Beispiel dafür :

& oder & attr - bietet eine Möglichkeit, einen Ausdruck im Kontext des übergeordneten Bereichs auszuführen. Wenn kein attr-Name angegeben wird, wird angenommen, dass der Attributname mit dem lokalen Namen übereinstimmt. Gegebene und Widget-Definition des Gültigkeitsbereichs: {localFn: '& myAttr'}. Anschließend zeigt die isolierte Bereichseigenschaft localFn auf einen Funktionswrapper für den Ausdruck count = count + value. Oft ist es wünschenswert, Daten aus dem isolierten Bereich über einen Ausdruck an den übergeordneten Bereich zu übergeben. Dies kann durch Übergeben einer Zuordnung lokaler Variablennamen und -werte an den Ausdrucks-Wrapper fn erfolgen. Wenn der Ausdruck beispielsweise Inkrement (Betrag) ist, können wir den Betragswert angeben, indem wir localFn als localFn aufrufen ({Betrag: 22}).

Chandermani
quelle
4
Sehr schön! Ist das irgendwo dokumentiert?
Ach
12
Ich denke nicht, dass es eine gute Lösung ist, weil in der Direktivendefinition manchmal man nicht weiß, welcher Parameter übergeben werden soll.
OMGPOP
Dies ist eine gute Lösung und ich danke Ihnen dafür, aber ich glaube, die Antwort braucht ein bisschen Aufholjagd. Wer ist lex82 und was hat er erwähnt?
Wtower
Interessanter Ansatz. Obwohl was passiert, wenn Sie zulassen möchten, dass eine Funktion mit einem beliebigen Parameter (oder mehreren) übergeben wird? Sie kennen weder die Funktion noch ihre Parameter und müssen sie bei einem Ereignis innerhalb der Direktive ausführen. Wie geht man vor? Zum Beispiel könnten Sie für eine Direktive onchangefunc = 'myCtrlFunc (dynamicVariableHere)'
trainoasis
58

An den anderen Antworten ist nichts auszusetzen, aber ich verwende die folgende Technik, wenn ich Funktionen in einem Direktivenattribut übergebe.

Lassen Sie die Klammern weg, wenn Sie die Direktive in Ihr HTML aufnehmen:

<my-directive callback="someFunction" />

Dann "entpacken" Sie die Funktion im Link oder Controller Ihrer Direktive. Hier ist ein Beispiel:

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

Mit dem Schritt "Entpacken" kann die Funktion mit einer natürlicheren Syntax aufgerufen werden. Außerdem wird sichergestellt, dass die Direktive auch dann ordnungsgemäß funktioniert, wenn sie in anderen Direktiven verschachtelt ist, die die Funktion möglicherweise übergeben. Wenn Sie das Auspacken nicht durchgeführt haben, haben Sie ein Szenario wie das folgende:

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

Dann würden Sie so etwas in Ihrer inneren Richtlinie finden:

callback()()()(data); 

Was in anderen Verschachtelungsszenarien fehlschlagen würde.

Ich habe diese Technik aus einem ausgezeichneten Artikel von Dan Wahlin unter http://weblogs.asp.net/dwahlin/creating-custom-angularjs-directives-part-3-isolate-scope-and-function-parameters angepasst

Ich habe den Entpackungsschritt hinzugefügt, um das Aufrufen der Funktion natürlicher zu gestalten und das Verschachtelungsproblem zu lösen, auf das ich in einem Projekt gestoßen bin.

ItsCosmo
quelle
2
Ein netter Ansatz, aber ich kann den thisZeiger nicht innerhalb der Rückrufmethode verwenden, da er den Geltungsbereich der Direktive verwendet. Ich benutze Typescript und mein Rückruf sieht folgendermaßen aus: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
Hinweis: Wenn Sie verschachtelte Anweisungen haben und den Rückruf nach oben weitergeben möchten, müssen Sie jede Anweisung auspacken, nicht nur diejenige, die den Rückruf auslöst.
Episodex
43

In der Direktive ( myDirective):

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

In der Richtlinienvorlage:

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

In der Quelle:

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

...wo myFunction in der Steuerung definiert ist.

Beachten Sie, dass paramdie Vorlage in der Direktive paramin der Quelle sauber gebunden ist und auf gesetzt ist item.


linkVerwenden Sie einen sehr ähnlichen Ansatz, um aus der Eigenschaft einer Direktive ("innerhalb" derselben) heraus aufzurufen :

...
directive.link = function(isolatedScope) {
    isolatedScope.boundFunction({param: "foo"});
};
...
return directive;
Ben
quelle
Während Sie In source haben: bound-function = 'myFunction (obj1.param, obj2.param)'> wie geht es dann weiter?
Ankit Pandey
15

Ja, es gibt einen besseren Weg: Sie können den $ parse-Dienst in Ihrer Direktive verwenden, um einen Ausdruck im Kontext des übergeordneten Bereichs auszuwerten, während Sie bestimmte Bezeichner im Ausdruck an Werte binden, die nur in Ihrer Direktive sichtbar sind:

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

Fügen Sie diese Zeile der Verknüpfungsfunktion der Direktive hinzu, über die Sie auf die Attribute der Direktive zugreifen können.

Ihr callback = "callback(item.id, arg2)"Rückrufattribut kann dann wie folgt festgelegt werden, da arg2 vom $ parse-Dienst in der Direktive an yourSecondArgument gebunden wird. Mit Direktiven wie können ng-clickSie über den $eventBezeichner innerhalb des an die Direktive übergebenen Ausdrucks auf das Klickereignis zugreifen, indem Sie genau diesen Mechanismus verwenden.

Beachten Sie, dass Sie callbackmit dieser Lösung kein Mitglied Ihres isolierten Bereichs machen müssen.

lex82
quelle
3
Durch scope.$parentdie Verwendung wird die Direktive "undicht" - sie "kennt" zu viel von der Außenwelt, was eine gut gestaltete gekapselte Komponente nicht sollte.
Dmitri Zaitsev
3
Nun, es weiß, dass es einen übergeordneten Bereich hat, aber es greift nicht auf ein bestimmtes Feld im Bereich zu, daher denke ich, dass dies tolerierbar ist.
Lex82
0

Für mich hat folgendes funktioniert:

in der Richtlinie erklären Sie es so:

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

Verwenden Sie die Direktivenvorlage folgendermaßen:

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

Wenn Sie dann die Direktive verwenden, übergeben Sie die Funktion wie folgt:

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

Sie übergeben die Funktion durch ihre Deklaration und sie wird aus der Direktive aufgerufen und Parameter werden ausgefüllt.

michal.jakubeczy
quelle