Chiama una funzione controller da una direttiva senza ambito isolato in AngularJS


95

Non riesco a trovare un modo per chiamare una funzione sull'ambito padre dall'interno di una direttiva senza utilizzare l'ambito isolato. So che se utilizzo l'ambito isolato posso semplicemente usare "&" nell'isolato per accedere alla funzione sull'ambito padre, ma l'uso dell'ambito isolato quando non è necessario ha delle conseguenze. Considera il seguente HTML:

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

In questo semplice esempio, voglio mostrare una finestra di dialogo di conferma JavaScript e chiamare doIt () solo se fanno clic su "OK" nella finestra di dialogo di conferma. Questo è semplice utilizzando un ambito isolato. La direttiva sarebbe simile a questa:

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

Ma il problema è che, poiché sto usando uno scope isolato, ng-hide nell'esempio sopra non viene più eseguito sullo scope genitore , ma piuttosto nello scope isolato (poiché l'uso di uno scope isolato su qualsiasi direttiva fa sì che tutte le direttive su quell'elemento utilizzare l'ambito isolato). Ecco un jsFiddle dell'esempio precedente in cui ng-hide non funziona. (Nota che in questo violino, il pulsante dovrebbe nascondersi quando digiti "sì" nella casella di immissione.)

L'alternativa sarebbe NON utilizzare un campo di applicazione isolato , che in realtà è ciò che desidero veramente qui poiché non è necessario isolare il campo di applicazione di questa direttiva. L'unico problema che ho è, come posso chiamare un metodo sull'ambito padre se non lo passo su un ambito isolato ?

Ecco un jsfiddle in cui NON sto usando l'ambito isolato e ng-hide funziona bene, ma, ovviamente, la chiamata a confirmAction () non funziona e non so come farlo funzionare.

Si prega di notare, la risposta che sto davvero cercando è come chiamare le funzioni sull'ambito esterno SENZA utilizzare un ambito isolato. E non mi interessa far funzionare questo dialogo di conferma in un altro modo, perché il punto di questa domanda è capire come effettuare chiamate all'ambito esterno ed essere ancora in grado di far funzionare altre direttive contro l'ambito genitore.

In alternativa, sarei interessato a conoscere soluzioni che utilizzano un ambito isolato se altre direttive continueranno a funzionare contro l'ambito padre , ma non credo che ciò sia possibile.


Per i passanti, Dan Wahlin ha scritto un ottimo articolo che spiega l'ambito dell'isolato e i parametri della funzione: weblogs.asp.net/dwahlin/…
tanguy_k

Risposte:


117

Poiché la direttiva chiama solo una funzione (e non cerca di impostare un valore su una proprietà), puoi usare $ eval invece di $ parse (con un ambito non isolato):

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

O meglio, usa semplicemente $ apply , che $ eval () userà il suo argomento contro lo scope:

scope.$apply(attrs.confirmAction);

Violino


Non lo sapevo, grazie per quello. Ho sempre pensato che il metodo $ parse fosse un po 'troppo prolisso.
Clark Pan

2
come impostereste i parametri su quella funzione?
CMCDragonkai

2
@CMCDragonkai, se la funzione ha argomenti, puoi specificarli nell'HTML confirm-action="doIt(arg1)", e poi impostarli scope.arg1prima di chiamare $ eval. Tuttavia, sarebbe probabilmente più pulito per l'uso $ parse: stackoverflow.com/a/16200618/215945
Mark Rajcok

Non è possibile fare scope. $ Eval (attrs.doIt ({arg1: 'blah'}) ;?
CMCDragonkai

3
@CMCDragonkai, no, scope.$eval(attrs.confirmAction({arg1: 'blah'})non funzionerà. Dovresti usare $ parse con quella sintassi.
Mark Rajcok

17

Vuoi utilizzare il servizio $ parse in AngularJS.

Quindi per il tuo esempio:

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

C'è un trucco da fare con l'impostazione di una proprietà usando $ parse, ma ti lascerò attraversare quel ponte quando ci arrivi.


Grazie, Clark, questo è esattamente quello che stavo cercando. Avevo provato a utilizzare $ parse, ma non sono riuscito a passare l'ambito quindi non funzionava per me. Questo è perfetto.
Jim Cooper,

Sono stato sorpreso di scoprire che questa soluzione funziona anche senza scopo: vero. La mia comprensione era tale ambito: vero è ciò che fa sì che l'ambito della direttiva erediti prototipicamente dall'ambito padre. Qualche idea sul perché funzioni ancora senza di essa?
Jim Cooper

2
nessun ambito figlio (eliminazione scope:true) funziona perché la direttiva non creerà affatto un nuovo ambito, e quindi la scopeproprietà a cui si fa riferimento nella linkfunzione è esattamente lo stesso ambito dell'ambito "genitore" in precedenza.
Clark Pan

$ apply è sufficiente per questo caso (vedi la mia risposta).
Mark Rajcok

1
A cosa serve $ event?
CMCDragonkai

12

Richiama esplicitamente hideButtonl'ambito padre.

Ecco il violino: http://jsfiddle.net/pXej2/5/

Ed ecco l'HTML aggiornato:

<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>

+1 per una soluzione funzionante. In realtà avevo provato a usare $ genitore e quando non funzionava mi arrendevo. Dopo aver visto la tua soluzione, ho dato un'occhiata più da vicino e si è scoperto che non riuscivo ad aggiungere $ parent ai parametri che stavo passando (non mostrato nel mio violino originale). Ad esempio, se volessi passare showIt alla funzione hideButton () avrei bisogno di usare $ parent.hideButton ($ parent.showIt) e mi mancava il secondo $ parent. Accetterò la risposta di Clark, tuttavia, poiché quella soluzione non richiede che l'utente della mia direttiva sappia di dover aggiungere $ parent. Grazie per l'intuizione!
Jim Cooper,

1

L'unico problema che ho è, come posso chiamare un metodo sull'ambito padre se non lo passo su un ambito isolato?

Ecco un jsfiddle in cui NON sto usando l'ambito isolato e ng-hide funziona bene, ma, ovviamente, la chiamata a confirmAction () non funziona e non so come farlo funzionare.

Prima un piccolo punto: in questo caso l'ambito del controllore esterno non è l'ambito padre dell'ambito della direttiva; ambito del controllore esterno è campo di applicazione della direttiva. In altre parole, i nomi delle variabili utilizzati nella direttiva verranno cercati direttamente nell'ambito del controller.

Successivamente, la scrittura attrs.confirmAction()non funziona perché attrs.confirmActionper questo pulsante,

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

è la stringa "doIt()"e non puoi chiamare una stringa, ad es "doIt()"().

La tua domanda è davvero:

Come posso chiamare una funzione quando ho il suo nome come stringa?

In JavaScript, per lo più chiami le funzioni utilizzando la notazione punto, in questo modo:

some_obj.greet()

Ma puoi anche usare la notazione dell'array:

some_obj['greet']

Nota come il valore dell'indice sia una stringa . Quindi, nella tua direttiva puoi semplicemente fare questo:

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

+1 poiché l'idea di analizzare la funzione era complicata e mi ha aiutato con un problema simile ma un altro. Grazie!
Anmol Saraf
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.