Wywołaj funkcję kontrolera z dyrektywy bez izolowanego zakresu w AngularJS

95

Nie mogę znaleźć sposobu na wywołanie funkcji z zakresu nadrzędnego z poziomu dyrektywy bez użycia zakresu izolowanego. Wiem, że jeśli używam zakresu izolowanego, mogę po prostu użyć „&” w izolowanym, aby uzyskać dostęp do funkcji w zakresie nadrzędnym, ale użycie zakresu izolowanego, gdy nie jest to konieczne, ma konsekwencje. Rozważ następujący kod HTML:

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

W tym prostym przykładzie chcę wyświetlić okno dialogowe potwierdzenia JavaScript i wywołać funkcję doIt () tylko wtedy, gdy klikną „OK” w oknie dialogowym potwierdzenia. Jest to proste przy użyciu izolowanego zakresu. Dyrektywa wyglądałaby następująco:

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

Ale problem polega na tym, że ponieważ używam zakresu izolowanego, ng-hide w powyższym przykładzie nie jest już wykonywany względem zakresu nadrzędnego , ale raczej w zakresie izolowanym (ponieważ użycie zakresu izolowanego w dowolnej dyrektywie powoduje, że wszystkie dyrektywy tego elementu użyć izolowanego zakresu). Oto jsFiddle z powyższego przykładu, w którym ng-hide nie działa. (Zauważ, że w tych skrzypcach przycisk powinien się ukryć po wpisaniu „tak” w polu wprowadzania).

Alternatywą byłoby NIE stosowanie izolowanego zakresu , czego tak naprawdę tutaj chcę, ponieważ nie ma potrzeby izolowania zakresu tej dyrektywy. Jedyny problem, jaki mam, polega na tym , jak wywołać metodę w zakresie nadrzędnym, jeśli nie przekażę jej w zakresie izolowanym ?

Oto jsfiddle, w którym NIE używam izolowanego zasięgu, a ng-hide działa dobrze, ale oczywiście wywołanie confirmAction () nie działa i nie wiem, jak to zrobić.

Proszę zauważyć, że odpowiedź, której naprawdę szukam, to wywoływanie funkcji w zakresie zewnętrznym BEZ stosowania zakresu izolowanego. I nie jestem zainteresowany, aby ten dialog potwierdzający działał w inny sposób, ponieważ celem tego pytania jest ustalenie, jak wywoływać zakres zewnętrzny i nadal być w stanie sprawić, by inne dyrektywy działały przeciwko zakresowi nadrzędnemu.

Ewentualnie chciałbym usłyszeć o rozwiązaniach wykorzystujących zakres izolowany, jeśli inne dyrektywy będą nadal działać przeciwko zakresowi nadrzędnemu , ale nie sądzę, aby było to możliwe.

Jim Cooper
źródło
Dla przechodniów Dan Wahlin napisał bardzo dobry artykuł wyjaśniający zakres izolowania i parametry funkcji: weblogs.asp.net/dwahlin/ ...
tanguy_k

Odpowiedzi:

117

Ponieważ dyrektywa tylko wywołuje funkcję (a nie próbuje ustawić wartości właściwości), możesz użyć $ eval zamiast $ parse (z nieizolowanym zakresem):

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

Lub lepiej, po prostu użyj $ Apply , co spowoduje $ eval () ułoży swój argument przeciwko zakresowi:

scope.$apply(attrs.confirmAction);

Skrzypce

Mark Rajcok
źródło
Nie wiedziałem, dzięki za to. Zawsze uważałem, że metoda $ parse jest zbyt rozwlekła.
Clark Pan,
2
jak ustawić parametry tej funkcji?
CMCDragonkai
2
@CMCDragonkai, jeśli funkcja ma argumenty, możesz określić je w kodzie HTML confirm-action="doIt(arg1)", a następnie ustawić scope.arg1przed wywołaniem $ eval. Jednak prawdopodobnie czystsze byłoby użycie $ parse: stackoverflow.com/a/16200618/215945
Mark Rajcok
Czy nie można zrobić zasięgu. $ Eval (attrs.doIt ({arg1: 'blah'}) ;?
CMCDragonkai
3
@CMCDragonkai, nie, scope.$eval(attrs.confirmAction({arg1: 'blah'})nie będzie działać. Będziesz musiał użyć $ parse z taką składnią.
Mark Rajcok,
17

Chcesz skorzystać z usługi $ parse w AngularJS.

Na przykład:

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

Jest pewna pułapka związana z ustawieniem właściwości za pomocą $ parse, ale pozwolę ci przejść przez ten most, kiedy do niego dojdziesz.

Clark Pan
źródło
Dzięki, Clark, właśnie tego szukałem. Próbowałem użyć $ parse, ale nie udało mi się przekazać zakresu, więc nie działało to dla mnie. To jest doskonałe.
Jim Cooper,
Zaskoczyło mnie, że to rozwiązanie działa również bez zakresu: prawda. Rozumiem, że zakres: prawda jest tym, co sprawia, że ​​zakres dyrektywy prototypowo dziedziczy po zakresie nadrzędnym. Masz jakiś pomysł, dlaczego to nadal działa bez niego?
Jim Cooper,
2
żaden zakres potomny (usuwanie scope:true) nie działa, ponieważ dyrektywa w ogóle nie utworzy nowego zakresu, a zatem scopewłaściwość, do której odwołujesz się w linkfunkcji, jest dokładnie tym samym zakresem, co poprzednio zakres „nadrzędny”.
Clark Pan,
W tym przypadku wystarczy $ Apply (zobacz moją odpowiedź).
Mark Rajcok,
1
Do czego służy wydarzenie $?
CMCDragonkai,
12

Jawnie wywołaj hideButtonzakres nadrzędny.

Oto skrzypce: http://jsfiddle.net/pXej2/5/

A oto zaktualizowany kod 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
źródło
+1 za działające rozwiązanie. Próbowałem użyć $ parent, a kiedy to nie zadziałało, poddałem się. Po obejrzeniu twojego rozwiązania przyjrzałem się bliżej i okazało się, że nie udało mi się dodać $ parent do przekazywanych parametrów (nie pokazanych na moich oryginalnych skrzypcach). Na przykład, gdybym chciał przekazać showIt do funkcji hideButton (), musiałbym użyć $ parent.hideButton ($ parent.showIt) i brakowało mi drugiego rodzica $ parent. Przyjmuję jednak odpowiedź Clarka, ponieważ to rozwiązanie nie wymaga od użytkownika mojej dyrektywy, aby wiedział, że musi dodać element $ parent. Dzięki za wgląd!
Jim Cooper,
1

Jedynym problemem, jaki mam, jest to, jak wywołać metodę w zakresie nadrzędnym, jeśli nie przekażę jej w zakresie izolowanym?

Oto jsfiddle, w którym NIE używam izolowanego zakresu, a ng-hide działa dobrze, ale oczywiście wywołanie confirmAction () nie działa i nie wiem, jak to zrobić.

Najpierw mała uwaga: w tym przypadku zakres administratora zewnętrznego nie jest nadrzędnym zakresem dyrektywy; Zakres zewnętrznej regulatora jest zakres dyrektywy. Innymi słowy, nazwy zmiennych użyte w dyrektywie będą wyszukiwane bezpośrednio w zakresie kontrolera.

Następnie pisanie attrs.confirmAction()nie działa, ponieważ attrs.confirmActiondla tego przycisku

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

jest napisem "doIt()", a nie możesz wywołać łańcucha, np "doIt()"().

Twoje pytanie naprawdę brzmi:

Jak wywołać funkcję, gdy mam jej nazwę jako ciąg?

W JavaScript wywołujesz funkcje głównie przy użyciu notacji kropkowej, na przykład:

some_obj.greet()

Ale możesz też użyć notacji tablicowej:

some_obj['greet']

Zwróć uwagę, że wartość indeksu jest ciągiem . Tak więc w swojej dyrektywie możesz po prostu zrobić to:

  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
źródło
+1, ponieważ pomysł parsowania funkcji był trudny i pomógł mi z podobnym, ale innym problemem. Dzięki!
Anmol Saraf