Rufen Sie eine Controller-Funktion aus einer Direktive ohne isolierten Bereich in AngularJS auf

95

Ich kann anscheinend keine Möglichkeit finden, eine Funktion für den übergeordneten Bereich innerhalb einer Direktive aufzurufen, ohne einen isolierten Bereich zu verwenden. Ich weiß, dass ich bei Verwendung eines isolierten Bereichs nur "&" im isolierten Bereich verwenden kann, um auf die Funktion im übergeordneten Bereich zuzugreifen. Die Verwendung eines isolierten Bereichs, wenn dies nicht erforderlich ist, hat jedoch Konsequenzen. Betrachten Sie den folgenden HTML-Code:

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

In diesem einfachen Beispiel möchte ich ein JavaScript-Bestätigungsdialogfeld anzeigen und doIt () nur aufrufen, wenn sie im Bestätigungsdialogfeld auf "OK" klicken. Dies ist mit einem isolierten Bereich einfach. Die Richtlinie würde folgendermaßen aussehen:

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

Das Problem ist jedoch, dass ng-hide im obigen Beispiel nicht mehr für den übergeordneten Bereich ausgeführt wird , sondern für den isolierten Bereich (da die Verwendung eines isolierten Bereichs für eine Direktive alle Anweisungen für dieses Element bewirkt , da ich einen isolierten Bereich verwende Verwenden Sie den isolierten Bereich. Hier ist eine jsFiddle des obigen Beispiels, bei der ng-hide nicht funktioniert. (Beachten Sie, dass in dieser Geige die Schaltfläche ausgeblendet sein sollte, wenn Sie "Ja" in das Eingabefeld eingeben.)

Die Alternative wäre, KEINEN isolierten Geltungsbereich zu verwenden , was ich hier eigentlich wirklich möchte, da der Geltungsbereich dieser Richtlinie nicht isoliert werden muss. Das einzige Problem, das ich habe, ist, wie ich eine Methode im übergeordneten Bereich aufrufe, wenn ich sie nicht im isolierten Bereich weitergebe .

Hier ist eine jsfiddle, bei der ich KEINEN isolierten Bereich verwende und das ng-hide einwandfrei funktioniert, aber natürlich funktioniert der Aufruf von verifyAction () nicht und ich weiß nicht, wie ich es zum Laufen bringen soll.

Bitte beachten Sie, dass die Antwort, nach der ich wirklich suche, darin besteht, Funktionen auf dem äußeren Bereich aufzurufen, OHNE einen isolierten Bereich zu verwenden. Und ich bin nicht daran interessiert, dass dieser Bestätigungsdialog auf andere Weise funktioniert, da es bei dieser Frage darum geht, herauszufinden, wie der äußere Bereich aufgerufen werden kann und dennoch andere Anweisungen gegen den übergeordneten Bereich arbeiten können.

Alternativ wäre ich interessiert, von Lösungen zu hören, die einen isolierten Bereich verwenden, wenn andere Richtlinien weiterhin gegen den übergeordneten Bereich arbeiten , aber ich denke nicht, dass dies möglich ist.

Jim Cooper
quelle
Für die Passanten hat Dan Wahlin einen sehr guten Artikel geschrieben, in dem Umfang und Funktionsparameter erläutert werden: weblogs.asp.net/dwahlin/…
tanguy_k

Antworten:

117

Da die Direktive nur eine Funktion aufruft (und nicht versucht, einen Wert für eine Eigenschaft festzulegen ), können Sie $ eval anstelle von $ parse (mit einem nicht isolierten Bereich) verwenden:

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

Oder besser, verwenden Sie einfach $ apply , wodurch $ eval () sein Argument gegen den Geltungsbereich aufgreift:

scope.$apply(attrs.confirmAction);

Geige

Mark Rajcok
quelle
Wusste das nicht, danke dafür. Ich habe immer gedacht, dass die $ parse-Methode etwas zu wortreich ist.
Clark Pan
2
Wie würden Sie Parameter für diese Funktion einstellen?
CMCDragonkai
2
@CMCDragonkai: Wenn die Funktion Argumente enthält, können Sie diese im HTML-Code angeben confirm-action="doIt(arg1)"und dann festlegen, scope.arg1bevor Sie $ eval aufrufen. Es wäre jedoch wahrscheinlich sauberer, $ parse
Mark Rajcok
Ist es nicht möglich, den Bereich auszuführen? $ Eval (attrs.doIt ({arg1: 'blah'});?
CMCDragonkai
3
@CMCDragonkai, nein, scope.$eval(attrs.confirmAction({arg1: 'blah'})wird nicht funktionieren. Sie müssten $ parse mit dieser Syntax verwenden.
Mark Rajcok
17

Sie möchten den $ parse-Dienst in AngularJS verwenden.

Also für Ihr Beispiel:

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

Es gibt ein Problem damit, eine Eigenschaft mit $ parse festzulegen, aber ich lasse Sie diese Brücke überqueren, wenn Sie dazu kommen.

Clark Pan
quelle
Danke, Clark, genau das habe ich gesucht. Ich hatte versucht, $ parse zu verwenden, konnte den Bereich jedoch nicht übergeben, sodass er bei mir nicht funktionierte. Dies ist perfekt.
Jim Cooper
Ich war überrascht, dass diese Lösung auch ohne Anwendungsbereich funktioniert: true. Mein Verständnis war dieser Geltungsbereich: Wahr ist, was den Geltungsbereich der Richtlinie prototypisch vom übergeordneten Geltungsbereich erben lässt. Irgendeine Idee, warum dies ohne es immer noch funktioniert?
Jim Cooper
2
Kein untergeordneter Bereich (Löschen scope:true) funktioniert, da die Direktive überhaupt keinen neuen Bereich erstellt. Daher ist die scopeEigenschaft, auf die Sie in der linkFunktion verweisen, genau derselbe Bereich wie der zuvor übergeordnete Bereich.
Clark Pan
$ apply ist für diesen Fall ausreichend (siehe meine Antwort).
Mark Rajcok
1
Wofür ist das $ Event?
CMCDragonkai
12

Rufen Sie explizit hideButtonden übergeordneten Bereich auf.

Hier ist die Geige: http://jsfiddle.net/pXej2/5/

Und hier ist der aktualisierte HTML:

<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
quelle
+1 für eine funktionierende Lösung. Ich hatte tatsächlich versucht, $ parent zu verwenden, und als es nicht funktionierte, gab ich auf. Nachdem ich Ihre Lösung gesehen hatte, schaute ich genauer hin und es stellte sich heraus, dass ich $ parent nicht zu den Parametern hinzufügen konnte, die ich übergab (in meiner ursprünglichen Geige nicht gezeigt). Wenn ich beispielsweise showIt an die Funktion hideButton () übergeben wollte, musste ich $ parent.hideButton ($ parent.showIt) verwenden, und mir fehlte das zweite $ parent. Ich werde jedoch Clarks Antwort akzeptieren, da der Benutzer meiner Direktive für diese Lösung nicht wissen muss, dass er $ parent hinzufügen muss. Danke für den Einblick!
Jim Cooper
1

Das einzige Problem, das ich habe, ist, wie ich eine Methode im übergeordneten Bereich aufrufe, wenn ich sie nicht im isolierten Bereich weitergebe.

Hier ist eine jsfiddle, bei der ich KEINEN isolierten Bereich verwende und das ng-hide einwandfrei funktioniert, aber natürlich funktioniert der Aufruf von verifyAction () nicht und ich weiß nicht, wie ich es zum Laufen bringen soll.

Zunächst ein kleiner Punkt: In diesem Fall ist der Geltungsbereich des äußeren Controllers nicht der übergeordnete Geltungsbereich des Geltungsbereichs der Richtlinie. den Anwendungsbereich der Außen Controller ist Anwendungsbereich der Richtlinie. Mit anderen Worten, Variablennamen, die in der Direktive verwendet werden, werden direkt im Bereich des Controllers nachgeschlagen.

Als nächstes attrs.confirmAction()funktioniert das Schreiben nicht, weil attrs.confirmActionfür diese Schaltfläche

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

ist die Zeichenfolge "doIt()", und Sie können keine Zeichenfolge aufrufen, z "doIt()"().

Ihre Frage ist wirklich:

Wie rufe ich eine Funktion auf, wenn ich ihren Namen als Zeichenfolge habe?

In JavaScript rufen Sie Funktionen meistens in Punktnotation auf:

some_obj.greet()

Sie können aber auch die Array-Notation verwenden:

some_obj['greet']

Beachten Sie, wie der Indexwert eine Zeichenfolge ist . In Ihrer Anweisung können Sie also einfach Folgendes tun:

  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
quelle
+1, da die Idee, die Funktion zu analysieren, schwierig war und mir bei ähnlichen, aber anderen Problemen half. Vielen Dank!
Anmol Saraf